Compare commits
10 Commits
1449f49af7
...
dfc724fa06
Author | SHA1 | Date | |
---|---|---|---|
|
dfc724fa06 | ||
|
31f0cf5f86 | ||
|
4c50e6baec | ||
|
2d2368529f | ||
|
49f3f36bdf | ||
|
0dd44bb30c | ||
|
bd4d040c4e | ||
|
e2cb1286f1 | ||
|
edeba9de8b | ||
|
b9c78c92b2 |
26
.luacheckrc
Normal file
26
.luacheckrc
Normal file
@ -0,0 +1,26 @@
|
||||
std = "lua51+minetest"
|
||||
unused_args = false
|
||||
allow_defined_top = true
|
||||
max_line_length = 999
|
||||
|
||||
stds.minetest = {
|
||||
read_globals = {
|
||||
"minetest",
|
||||
"VoxelManip",
|
||||
"VoxelArea",
|
||||
"PseudoRandom",
|
||||
"ItemStack",
|
||||
"default",
|
||||
table = {
|
||||
fields = {
|
||||
"copy",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
read_globals = {
|
||||
"toolranks",
|
||||
"toolranks_extras",
|
||||
"wielded_light",
|
||||
}
|
33
README.md
33
README.md
@ -13,15 +13,41 @@ Mods based on this API:
|
||||
|
||||
The settings are the following:
|
||||
```
|
||||
throwing.velocity_factor = 19
|
||||
throwing.horizontal_acceleration_factor = -3
|
||||
# Trajectory parameters
|
||||
throwing.vertical_acceleration = -10
|
||||
throwing.realistic_trajectory = false
|
||||
throwing.frictional_coefficient = -.5
|
||||
|
||||
# How the initial velocity of arrows is computed: simple, strength, or momentum
|
||||
throwing.velocity_mode = strength
|
||||
throwing.velocity_factor = 19
|
||||
|
||||
# Whether to allow placing an arrow as a node
|
||||
throwing.allow_arrow_placing = false
|
||||
|
||||
# Minimum time between two shots
|
||||
throwing.bow_cooldown = 0.2
|
||||
|
||||
# Whether to enable toolranks for bows
|
||||
throwing.toolranks = true
|
||||
```
|
||||
|
||||
### Trajectory parameters
|
||||
|
||||
By default, the trajectory of the arrow is a simple parabola. You can set the vertical acceleration (acceleration of gravity) using `throwing.vertical_acceleration`.
|
||||
|
||||
If you want a more realistic trajectory that uses a first-order modelling of air friction, you can set `throwing.realistic_trajectory` to true. In this mode, `throwing.frictional_coefficient` indicates the ratio between the friction force on the arrow and its velocity. It should be negative. The acceleration of the arrow is subsequently determined by dividing the force by the "mass" of the arrow, the reference mass of 1 being the mass of the steel arrow in `throwing_arrows`. Generally, the frictional coefficient should be quite small; a value of -1 will already drastically shorten the range of the arrow. The default is -0.5.
|
||||
|
||||
### Initial velocity computation
|
||||
|
||||
The mod provides three modes to compute the initial velocity of an arrow after it is shot: simple, strength and momentum.
|
||||
|
||||
In simple mode, the initial velocity of the arrow is always the same. The value of this velocity is controlled by the `throwing.velocity_factor` setting, which defaults to 19.
|
||||
|
||||
In strength mode (the default), the initial velocity of the arrow only depends on the bow that is used---the more expensive the bow, the faster the arrow. The bow strength is multiplied by the velocity factor to compute the speed of the arrow. For reference, the steel bow in `throwing_arrows` has a strength of about 1.
|
||||
|
||||
Finally, momentum mode is the most realistic. It computes the velocity of the arrow based on the bow strength, as in the strength mode, and on the mass of the arrow: the heavier the arrow, the slower it will be shot. It is called the momentum mode because, in this mode, the strength of a bow indicates the initial momentum of the arrow rather than its initial speed.
|
||||
|
||||
## API
|
||||
|
||||
There are two available functions in the mod API:
|
||||
@ -49,6 +75,8 @@ Definition: definition table, containing:
|
||||
* function spawn_arrow_entity(position, arrow, player): defaults to throwing.spawn_arrow_entity
|
||||
* sound: sound to be played when the bow is used
|
||||
* delay: delay before throwing the arrow
|
||||
* no_toolranks: If true, toolranks support is disabled for this item. Defaults to false.
|
||||
* strength: strength of the bow, see above. Defaults to 1.
|
||||
]]
|
||||
|
||||
-- Example:
|
||||
@ -66,6 +94,7 @@ Definition: definition table, containing:
|
||||
* tiles (essential): tiles of the arrow.
|
||||
* target (optional, defaulting to throwing.target_both): what the arrow is able to hit (throwing.target_node, throwing.target_object, throwing.target_both).
|
||||
* allow_protected (optional, defaulting to false): whether the arrow can be throw in a protected area
|
||||
* mass (optional, defaulting to 1): the mass of the arrow (see above)
|
||||
* on_hit_sound (optional): sound played when the arrow hits a node or an object.
|
||||
* on_hit(self, pos, last_pos, node, object, hitter, data) (optional but very useful): callback function:
|
||||
- pos: the position of the hit node or object.
|
||||
|
196
init.lua
196
init.lua
@ -7,6 +7,8 @@ throwing.target_node = 2
|
||||
throwing.target_both = 3
|
||||
|
||||
throwing.modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator("throwing")
|
||||
local use_toolranks = minetest.get_modpath("toolranks") and minetest.settings:get_bool("throwing.toolranks", true)
|
||||
|
||||
--------- Arrows functions ---------
|
||||
function throwing.is_arrow(itemstack)
|
||||
@ -23,65 +25,103 @@ function throwing.spawn_arrow_entity(pos, arrow, player)
|
||||
return minetest.registered_items[arrow].throwing_entity(pos, player)
|
||||
end
|
||||
else
|
||||
obj = minetest.add_entity(pos, "__builtin:item", arrow)
|
||||
return minetest.add_entity(pos, "__builtin:item", arrow)
|
||||
end
|
||||
end
|
||||
|
||||
local function shoot_arrow(itemstack, player, index, throw_itself, new_stack)
|
||||
local function apply_realistic_acceleration(obj, mass)
|
||||
if not minetest.settings:get_bool("throwing.realistic_trajectory", false) then
|
||||
return
|
||||
end
|
||||
|
||||
local vertical_acceleration = tonumber(minetest.settings:get("throwing.vertical_acceleration")) or -10
|
||||
local friction_coef = tonumber(minetest.settings:get("throwing.frictional_coefficient")) or -3
|
||||
|
||||
local velocity = obj:get_velocity()
|
||||
obj:set_acceleration({
|
||||
x = friction_coef * velocity.x / mass,
|
||||
y = friction_coef * velocity.y / mass + vertical_acceleration,
|
||||
z = friction_coef * velocity.z / mass
|
||||
})
|
||||
end
|
||||
|
||||
local function shoot_arrow(def, toolranks_data, player, bow_index, throw_itself, new_stack)
|
||||
local inventory = player:get_inventory()
|
||||
if not throw_itself then
|
||||
if index >= player:get_inventory():get_size("main") then
|
||||
local arrow_index
|
||||
if throw_itself then
|
||||
arrow_index = bow_index
|
||||
else
|
||||
if bow_index >= player:get_inventory():get_size("main") then
|
||||
return false
|
||||
end
|
||||
index = index + 1
|
||||
arrow_index = bow_index + 1
|
||||
end
|
||||
local arrow_stack = inventory:get_stack("main", index)
|
||||
local arrow_stack = inventory:get_stack("main", arrow_index)
|
||||
local arrow = arrow_stack:get_name()
|
||||
|
||||
local playerpos = player:get_pos()
|
||||
local pos = {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}
|
||||
local obj = (minetest.registered_items[itemstack:get_name()].spawn_arrow_entity or throwing.spawn_arrow_entity)(pos, arrow, player)
|
||||
local obj = (def.spawn_arrow_entity or throwing.spawn_arrow_entity)(pos, arrow, player)
|
||||
|
||||
local luaentity = obj:get_luaentity()
|
||||
|
||||
-- Set custom data in the entity
|
||||
luaentity.player = player:get_player_name()
|
||||
if not luaentity.item then
|
||||
luaentity.item = arrow
|
||||
end
|
||||
luaentity.data = {}
|
||||
luaentity.timer = 0
|
||||
luaentity.toolranks = toolranks_data -- May be nil if toolranks is disabled
|
||||
|
||||
if luaentity.on_throw then
|
||||
if luaentity:on_throw(pos, player, arrow_stack, index, luaentity.data) == false then
|
||||
if luaentity:on_throw(pos, player, arrow_stack, arrow_index, luaentity.data) == false then
|
||||
obj:remove()
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local dir = player:get_look_dir()
|
||||
local velocity_factor = tonumber(minetest.settings:get("throwing.velocity_factor")) or 19
|
||||
local horizontal_acceleration_factor = tonumber(minetest.settings:get("throwing.horizontal_acceleration_factor")) or -3
|
||||
local vertical_acceleration = tonumber(minetest.settings:get("throwing.vertical_acceleration")) or -10
|
||||
local velocity_factor = tonumber(minetest.settings:get("throwing.velocity_factor")) or 19
|
||||
local velocity_mode = minetest.settings:get("throwing.velocity_mode") or "strength"
|
||||
|
||||
obj:set_velocity({x=dir.x*velocity_factor, y=dir.y*velocity_factor, z=dir.z*velocity_factor})
|
||||
obj:set_acceleration({x=dir.x*horizontal_acceleration_factor, y=vertical_acceleration, z=dir.z*horizontal_acceleration_factor})
|
||||
local velocity
|
||||
if velocity_mode == "simple" then
|
||||
velocity = velocity_factor
|
||||
elseif velocity_mode == "momentum" then
|
||||
velocity = def.strength * velocity_factor / luaentity.mass
|
||||
else
|
||||
velocity = def.strength * velocity_factor
|
||||
end
|
||||
|
||||
obj:set_velocity({
|
||||
x = dir.x * velocity,
|
||||
y = dir.y * velocity,
|
||||
z = dir.z * velocity
|
||||
})
|
||||
obj:set_acceleration({x = 0, y = vertical_acceleration, z = 0})
|
||||
obj:set_yaw(player:get_look_horizontal()-math.pi/2)
|
||||
|
||||
apply_realistic_acceleration(obj, luaentity.mass)
|
||||
|
||||
if luaentity.on_throw_sound ~= "" then
|
||||
minetest.sound_play(luaentity.on_throw_sound or "throwing_sound", {pos=playerpos, gain = 0.5})
|
||||
end
|
||||
|
||||
if not minetest.settings:get_bool("creative_mode") then
|
||||
if new_stack then
|
||||
inventory:set_stack("main", index, new_stack)
|
||||
else
|
||||
local stack = inventory:get_stack("main", index)
|
||||
stack:take_item()
|
||||
inventory:set_stack("main", index, stack)
|
||||
end
|
||||
inventory:set_stack("main", arrow_index, new_stack)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function arrow_step(self, dtime)
|
||||
function throwing.arrow_step(self, dtime)
|
||||
if not self.timer or not self.player then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
self.timer = self.timer + dtime
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(pos)
|
||||
@ -90,7 +130,7 @@ local function arrow_step(self, dtime)
|
||||
minetest.log(level or "action", "[throwing] Arrow "..(self.item or self.name).." throwed by player "..self.player.." "..tostring(self.timer).."s ago "..message)
|
||||
end
|
||||
|
||||
local hit = function(pos, node, obj)
|
||||
local hit = function(pos1, node1, obj)
|
||||
if obj then
|
||||
if obj:is_player() then
|
||||
if obj:get_player_name() == self.player then -- Avoid hitting the hitter
|
||||
@ -109,7 +149,7 @@ local function arrow_step(self, dtime)
|
||||
player:get_inventory():add_item("main", self.item)
|
||||
end
|
||||
if self.on_hit_fails then
|
||||
self:on_hit_fails(pos, player, self.data)
|
||||
self:on_hit_fails(pos1, player, self.data)
|
||||
end
|
||||
end
|
||||
|
||||
@ -119,14 +159,14 @@ local function arrow_step(self, dtime)
|
||||
return
|
||||
end
|
||||
|
||||
if node and minetest.is_protected(pos, self.player) and not self.allow_protected then -- Forbid hitting nodes in protected areas
|
||||
minetest.record_protection_violation(pos, self.player)
|
||||
if node1 and minetest.is_protected(pos1, self.player) and not self.allow_protected then -- Forbid hitting nodes in protected areas
|
||||
minetest.record_protection_violation(pos1, self.player)
|
||||
logging("hitted a node into a protected area")
|
||||
return
|
||||
end
|
||||
|
||||
if self.on_hit then
|
||||
local ret, reason = self:on_hit(pos, self.last_pos, node, obj, player, self.data)
|
||||
local ret, reason = self:on_hit(pos1, self.last_pos, node1, obj, player, self.data)
|
||||
if ret == false then
|
||||
if reason then
|
||||
logging(": on_hit function failed for reason: "..reason)
|
||||
@ -140,17 +180,33 @@ local function arrow_step(self, dtime)
|
||||
end
|
||||
|
||||
if self.on_hit_sound then
|
||||
minetest.sound_play(self.on_hit_sound, {pos = pos, gain = 0.8})
|
||||
minetest.sound_play(self.on_hit_sound, {pos = pos1, gain = 0.8})
|
||||
end
|
||||
if node then
|
||||
logging("collided with node "..node.name.." at ("..pos.x..","..pos.y..","..pos.z..")")
|
||||
|
||||
local identifier
|
||||
if node1 then
|
||||
identifier = "node " .. node1.name
|
||||
elseif obj then
|
||||
if obj:get_luaentity() then
|
||||
logging("collided with luaentity "..obj:get_luaentity().name.." at ("..pos.x..","..pos.y..","..pos.z..")")
|
||||
identifier = "luaentity " .. obj:get_luaentity().name
|
||||
elseif obj:is_player() then
|
||||
logging("collided with player "..obj:get_player_name().." at ("..pos.x..","..pos.y..","..pos.z..")")
|
||||
identifier = "player " .. obj:get_player_name()
|
||||
else
|
||||
logging("collided with object at ("..pos.x..","..pos.y..","..pos.z..")")
|
||||
identifier = "unknown object"
|
||||
end
|
||||
end
|
||||
if identifier then
|
||||
logging("collided with " .. identifier .. " at " .. minetest.pos_to_string(pos1) .. ")")
|
||||
end
|
||||
|
||||
-- Toolranks support: update bow uses
|
||||
if self.toolranks then
|
||||
local inventory = player:get_inventory()
|
||||
-- Check that the player did not move the bow
|
||||
local current_stack = inventory:get_stack("main", self.toolranks.index)
|
||||
if current_stack:get_name() == self.toolranks.name then
|
||||
local new_itemstack = toolranks.new_afteruse(current_stack, player, nil, {wear = self.toolranks.wear})
|
||||
inventory:set_stack("main", self.toolranks.index, new_itemstack)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -200,14 +256,14 @@ local function arrow_step(self, dtime)
|
||||
wielded_light.update_light_by_item(self.item, self.object:get_pos())
|
||||
end
|
||||
|
||||
apply_realistic_acceleration(self.object, self.mass) -- Physics: air friction
|
||||
|
||||
self.last_pos = pos -- Used by the build arrow
|
||||
end
|
||||
|
||||
-- Backwards compatibility
|
||||
function throwing.make_arrow_def(def)
|
||||
def.timer = 0
|
||||
def.player = ""
|
||||
def.on_step = arrow_step
|
||||
def.data = {}
|
||||
def.on_step = throwing.arrow_step
|
||||
return def
|
||||
end
|
||||
|
||||
@ -234,17 +290,18 @@ function throwing.register_arrow(name, def)
|
||||
if not def.groups.dig_immediate then
|
||||
def.groups.dig_immediate = 3
|
||||
end
|
||||
|
||||
def.inventory_image = def.tiles[1]
|
||||
def.on_place = function(itemstack, placer, pointed_thing)
|
||||
if minetest.settings:get_bool("throwing.allow_arrow_placing") and pointed_thing.above then
|
||||
local playername = placer:get_player_name()
|
||||
if not minetest.is_protected(pointed_thing.above, playername) then
|
||||
minetest.log("action", "Player "..playername.." placed arrow "..name.." at ("..pointed_thing.above.x..","..pointed_thing.above.y..","..pointed_thing.above.z..")")
|
||||
minetest.log("action", "Player "..playername.." placed arrow "..name.." at "..minetest.pos_to_string(pointed_thing.above))
|
||||
minetest.set_node(pointed_thing.above, {name = name})
|
||||
itemstack:take_item()
|
||||
return itemstack
|
||||
else
|
||||
minetest.log("warning", "Player "..playername.." tried to place arrow "..name.." into a protected area at ("..pointed_thing.above.x..","..pointed_thing.above.y..","..pointed_thing.above.z..")")
|
||||
minetest.log("warning", "Player "..playername.." tried to place arrow "..name.." into a protected area at "..minetest.pos_to_string(pointed_thing.above))
|
||||
minetest.record_protection_violation(pointed_thing.above, playername)
|
||||
return itemstack
|
||||
end
|
||||
@ -276,7 +333,7 @@ function throwing.register_arrow(name, def)
|
||||
}
|
||||
minetest.register_node(registration_name, def)
|
||||
|
||||
minetest.register_entity(registration_name.."_entity", throwing.make_arrow_def{
|
||||
minetest.register_entity(registration_name.."_entity", {
|
||||
physical = false,
|
||||
visual = "wielditem",
|
||||
visual_size = {x = 0.125, y = 0.125},
|
||||
@ -289,24 +346,44 @@ function throwing.register_arrow(name, def)
|
||||
allow_protected = def.allow_protected,
|
||||
target = def.target,
|
||||
on_hit_fails = def.on_hit_fails,
|
||||
on_step = throwing.arrow_step,
|
||||
item = name,
|
||||
mass = def.mass or 1,
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
---------- Bows -----------
|
||||
if use_toolranks and minetest.get_modpath("toolranks_extras") and toolranks_extras.register_tool_type then
|
||||
toolranks_extras.register_tool_type("bow", S("bow"), S("Arrows thrown"))
|
||||
end
|
||||
|
||||
function throwing.register_bow(name, def)
|
||||
local enable_toolranks = use_toolranks and not def.no_toolranks
|
||||
|
||||
def.name = name
|
||||
|
||||
if not def.allow_shot then
|
||||
def.allow_shot = function(player, itemstack, index)
|
||||
if index >= player:get_inventory():get_size("main") and not def.throw_itself then
|
||||
return false
|
||||
end
|
||||
return throwing.is_arrow(itemstack)
|
||||
return throwing.is_arrow(itemstack) or def.throw_itself
|
||||
end
|
||||
end
|
||||
|
||||
if not def.inventory_image then
|
||||
def.inventory_image = def.texture
|
||||
end
|
||||
|
||||
if not def.strength then
|
||||
def.strength = 20
|
||||
end
|
||||
|
||||
local throw_sound = def.sound
|
||||
-- Reserved field by Minetest. Prevent leaking irrelevant data to the registration API
|
||||
def.sound = nil
|
||||
|
||||
def.on_use = function(itemstack, user, pointed_thing)
|
||||
-- Cooldown
|
||||
local meta = itemstack:get_meta()
|
||||
@ -325,33 +402,50 @@ function throwing.register_bow(name, def)
|
||||
end
|
||||
|
||||
-- Sound
|
||||
if def.sound then
|
||||
minetest.sound_play(def.sound, {to_player=user:get_player_name()})
|
||||
if throw_sound then
|
||||
minetest.sound_play(throw_sound, {to_player=user:get_player_name()})
|
||||
end
|
||||
|
||||
meta:set_int("delay", os.time() + (def.delay or 0))
|
||||
minetest.after(def.delay or 0, function()
|
||||
-- Re-check that the arrow can be thrown. Overwrite the new_stack
|
||||
local old_new_stack = new_stack
|
||||
res, new_stack = def.allow_shot(user, user:get_inventory():get_stack("main", arrow_index), arrow_index, true)
|
||||
if not new_stack then
|
||||
new_stack = old_new_stack
|
||||
end
|
||||
|
||||
local arrow_stack = user:get_inventory():get_stack("main", arrow_index)
|
||||
|
||||
res, new_stack = def.allow_shot(user, arrow_stack, arrow_index, true)
|
||||
if not res then
|
||||
return
|
||||
end
|
||||
|
||||
if not new_stack then
|
||||
new_stack = old_new_stack
|
||||
end
|
||||
if not new_stack then
|
||||
arrow_stack:take_item()
|
||||
new_stack = arrow_stack
|
||||
end
|
||||
|
||||
-- Shoot arrow
|
||||
if shoot_arrow(itemstack, user, bow_index, def.throw_itself, new_stack) then
|
||||
local uses = 65535 / (def.uses or 50)
|
||||
local toolranks_data
|
||||
if enable_toolranks then
|
||||
toolranks_data = {
|
||||
name = itemstack:get_name(),
|
||||
index = bow_index,
|
||||
wear = uses
|
||||
}
|
||||
end
|
||||
if shoot_arrow(def, toolranks_data, user, bow_index, def.throw_itself, new_stack) then
|
||||
if not minetest.settings:get_bool("creative_mode") then
|
||||
itemstack:add_wear(65535 / (def.uses or 50))
|
||||
itemstack:add_wear(uses)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if def.throw_itself then
|
||||
-- This is a bug. If we return ItemStack(nil), the player punches the entity,
|
||||
-- and if the entity if a __builtin:item, it gets back to his inventory.
|
||||
-- and if the entity is a __builtin:item, it gets back to his inventory.
|
||||
minetest.after(0.1, function()
|
||||
user:get_inventory():remove_item("main", itemstack)
|
||||
end)
|
||||
@ -362,5 +456,11 @@ function throwing.register_bow(name, def)
|
||||
end)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
if enable_toolranks then
|
||||
def.original_description = def.original_description or def.description
|
||||
def.description = toolranks.create_description(def.description)
|
||||
end
|
||||
|
||||
minetest.register_tool(name, def)
|
||||
end
|
||||
|
7
locale/template.txt
Normal file
7
locale/template.txt
Normal file
@ -0,0 +1,7 @@
|
||||
# textdomain: throwing
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
Arrows thrown=
|
||||
bow=
|
7
locale/throwing.fr.tr
Normal file
7
locale/throwing.fr.tr
Normal file
@ -0,0 +1,7 @@
|
||||
# textdomain: throwing
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
Arrows thrown=Flèches lancées
|
||||
bow=arc
|
Loading…
x
Reference in New Issue
Block a user