diff --git a/api.lua b/api.lua index ae47f68..1111d9e 100644 --- a/api.lua +++ b/api.lua @@ -236,6 +236,7 @@ function XBows.register_bow(self, name, def, override) wield_image = def.custom.wield_image_charged or def.custom.inventory_image_charged, groups = def.custom.groups_charged, wield_scale = {x = 2, y = 2, z = 1.5}, + range = 0, ---@param itemstack ItemStack ---@param user ObjectRef|nil ---@param pointed_thing PointedThingDef @@ -605,6 +606,7 @@ function XBows.shoot(self, itemstack, user, pointed_thing) user:get_player_name() ) + ---Handle HUD and 3d Quiver if is_arrow_from_quiver == 1 then XBowsQuiver:udate_or_create_hud(user, detached_inv:get_list('main'), found_arrow_stack_idx) @@ -618,7 +620,7 @@ function XBows.shoot(self, itemstack, user, pointed_thing) if not inv:is_empty('x_bows:arrow_inv') then XBowsQuiver:udate_or_create_hud(user, inv:get_list('x_bows:arrow_inv')) else - ---no ammo (fake stack) + ---no ammo (fake stack just for the HUD) XBowsQuiver:udate_or_create_hud(user, {ItemStack({ name = 'x_bows:no_ammo' })}) @@ -637,16 +639,6 @@ function XBows.shoot(self, itemstack, user, pointed_thing) local bow_name = x_bows_registered_bow_charged_def.custom.name local uses = x_bows_registered_bow_charged_def.custom.uses local crit_chance = x_bows_registered_bow_charged_def.custom.crit_chance - local bow_strength = x_bows_registered_bow_charged_def.custom.strength - local bow_strength_min = x_bows_registered_bow_charged_def.custom.strength_min - local bow_strength_max = x_bows_registered_bow_charged_def.custom.strength_max - local acc_x_min = x_bows_registered_bow_charged_def.custom.acc_x_min - local acc_y_min = x_bows_registered_bow_charged_def.custom.acc_y_min - local acc_z_min = x_bows_registered_bow_charged_def.custom.acc_z_min - local acc_x_max = x_bows_registered_bow_charged_def.custom.acc_x_max - local acc_y_max = x_bows_registered_bow_charged_def.custom.acc_y_max - local acc_z_max = x_bows_registered_bow_charged_def.custom.acc_z_max - local gravity = x_bows_registered_bow_charged_def.custom.gravity ---Arrow local projectile_entity = x_bows_registered_arrow_def.custom.projectile_entity ---Quiver @@ -655,11 +647,12 @@ function XBows.shoot(self, itemstack, user, pointed_thing) local _tool_capabilities = x_bows_registered_arrow_def.custom.tool_capabilities local quiver_xbows_def = x_bows_registered_quiver_def + ---@type EnityStaticDataAttrDef local staticdata = { _arrow_name = arrow_name, _bow_name = bow_name, - user_name = user:get_player_name(), - is_critical_hit = false, + _user_name = user:get_player_name(), + _is_critical_hit = false, _tool_capabilities = _tool_capabilities, _tflp = tflp, _add_damage = 0 @@ -668,13 +661,13 @@ function XBows.shoot(self, itemstack, user, pointed_thing) ---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 + staticdata._is_critical_hit = true end end ---speed multiply if quiver_xbows_def and quiver_xbows_def.custom.faster_arrows and quiver_xbows_def.custom.faster_arrows > 1 then - staticdata.faster_arrows_multiplier = quiver_xbows_def.custom.faster_arrows + staticdata._faster_arrows_multiplier = quiver_xbows_def.custom.faster_arrows end ---add quiver damage @@ -684,20 +677,23 @@ function XBows.shoot(self, itemstack, user, pointed_thing) ---sound local sound_name = x_bows_registered_bow_charged_def.custom.sound_shoot - if staticdata.is_critical_hit then + if staticdata._is_critical_hit then sound_name = x_bows_registered_bow_charged_def.custom.sound_shoot_crit end - meta:set_string('arrow_itemstack_string', '') - itemstack:set_name(bow_name) + ---stop punching close objects/nodes when shooting + minetest.after(0.2, function() + if user:get_wielded_item():get_name() == itemstack:get_name() then + user:set_wielded_item(ItemStack({name = bow_name, wear = itemstack:get_wear()})) + end + end) - local pos = user:get_pos() - local dir = user:get_look_dir() + local player_pos = user:get_pos() local obj = minetest.add_entity( { - x = pos.x, - y = pos.y + 1.5, - z = pos.z + x = player_pos.x, + y = player_pos.y + 1.5, + z = player_pos.z }, projectile_entity, minetest.serialize(staticdata) @@ -707,44 +703,6 @@ function XBows.shoot(self, itemstack, user, pointed_thing) return itemstack end - local strength_multiplier = tflp - - if strength_multiplier > _tool_capabilities.full_punch_interval then - strength_multiplier = 1 - - ---faster arrow, only on full punch interval - if staticdata.faster_arrows_multiplier then - strength_multiplier = strength_multiplier + (strength_multiplier / staticdata.faster_arrows_multiplier) - end - end - - if bow_strength_max and bow_strength_min then - bow_strength = math.random(bow_strength_min, bow_strength_max) - end - - ---acceleration - local acc_x = dir.x - local acc_y = gravity - local acc_z = dir.z - - if acc_x_min and acc_x_max then - acc_x = math.random(acc_x_min, acc_x_max) - end - - if acc_y_min and acc_y_max then - acc_y = math.random(acc_y_min, acc_y_max) - end - - if acc_z_min and acc_z_max then - acc_z = math.random(acc_z_min, acc_z_max) - end - - local strength = bow_strength * strength_multiplier - - obj:set_velocity(vector.multiply(dir, strength)) - obj:set_acceleration({x = acc_x, y = acc_y, z = acc_z}) - obj:set_yaw(minetest.dir_to_yaw(dir)) - if not self:is_creative(user:get_player_name()) then itemstack:add_wear(65535 / uses) end @@ -843,24 +801,9 @@ 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 - ---Function receive a "luaentity" table as `self`. Called when the object is instantiated. ---@param self EntityDef|EntityDefCustom|XBows ----@param selfObj table +---@param selfObj EnityCustomAttrDef ---@param staticdata string ---@param dtime_s? integer|number ---@return nil @@ -870,7 +813,7 @@ function XBowsEntityDef.on_activate(self, selfObj, staticdata, dtime_s) return end - local _staticdata = minetest.deserialize(staticdata) + local _staticdata = minetest.deserialize(staticdata)--[[@as EnityStaticDataAttrDef]] -- set/reset - do not inherit from previous entity table selfObj._velocity = {x = 0, y = 0, z = 0} @@ -888,12 +831,12 @@ function XBowsEntityDef.on_activate(self, selfObj, staticdata, dtime_s) selfObj._shot_from_pos = selfObj.object:get_pos() selfObj._arrow_name = _staticdata._arrow_name selfObj._bow_name = _staticdata._bow_name - selfObj._user_name = _staticdata.user_name - selfObj.user = minetest.get_player_by_name(_staticdata.user_name) + selfObj._user_name = _staticdata._user_name + selfObj._user = minetest.get_player_by_name(_staticdata._user_name) selfObj._tflp = _staticdata._tflp selfObj._tool_capabilities = _staticdata._tool_capabilities - selfObj._is_critical_hit = _staticdata.is_critical_hit - selfObj._faster_arrows_multiplier = _staticdata.faster_arrows_multiplier + selfObj._is_critical_hit = _staticdata._is_critical_hit + selfObj._faster_arrows_multiplier = _staticdata._faster_arrows_multiplier selfObj._add_damage = _staticdata._add_damage selfObj._caused_damage = 0 selfObj._caused_knockback = 0 @@ -903,9 +846,58 @@ function XBowsEntityDef.on_activate(self, selfObj, staticdata, dtime_s) selfObj._arrow_particle_effect_crit = x_bows_registered_arrow_def.custom.particle_effect_crit selfObj._arrow_particle_effect_fast = x_bows_registered_arrow_def.custom.particle_effect_fast + ---Bow Def local x_bows_registered_bow_def = self.registered_bows[selfObj._bow_name] selfObj._sound_hit = x_bows_registered_bow_def.custom.sound_hit + local bow_strength = x_bows_registered_bow_def.custom.strength + local acc_x_min = x_bows_registered_bow_def.custom.acc_x_min + local acc_y_min = x_bows_registered_bow_def.custom.acc_y_min + local acc_z_min = x_bows_registered_bow_def.custom.acc_z_min + local acc_x_max = x_bows_registered_bow_def.custom.acc_x_max + local acc_y_max = x_bows_registered_bow_def.custom.acc_y_max + local acc_z_max = x_bows_registered_bow_def.custom.acc_z_max + local gravity = x_bows_registered_bow_def.custom.gravity + local bow_strength_min = x_bows_registered_bow_def.custom.strength_min + local bow_strength_max = x_bows_registered_bow_def.custom.strength_max + ---acceleration + selfObj._player_look_dir = selfObj._user:get_look_dir() + + selfObj._acc_x = selfObj._player_look_dir.x + selfObj._acc_y = gravity + selfObj._acc_z = selfObj._player_look_dir.z + + if acc_x_min and acc_x_max then + selfObj._acc_x = math.random(acc_x_min, acc_x_max) + end + + if acc_y_min and acc_y_max then + selfObj._acc_y = math.random(acc_y_min, acc_y_max) + end + + if acc_z_min and acc_z_max then + selfObj._acc_z = math.random(acc_z_min, acc_z_max) + end + + ---strength + local strength_multiplier = selfObj._tflp + + if strength_multiplier > selfObj._tool_capabilities.full_punch_interval then + strength_multiplier = 1 + + ---faster arrow, only on full punch interval + if selfObj._faster_arrows_multiplier then + strength_multiplier = strength_multiplier + (strength_multiplier / selfObj._faster_arrows_multiplier) + end + end + + if bow_strength_max and bow_strength_min then + bow_strength = math.random(bow_strength_min, bow_strength_max) + end + + selfObj._strength = bow_strength * strength_multiplier + + ---rotation factor local x_bows_registered_entity_def = self.registered_entities[selfObj.name] selfObj._rotation_factor = x_bows_registered_entity_def._custom.rotation_factor @@ -913,15 +905,19 @@ function XBowsEntityDef.on_activate(self, selfObj, staticdata, dtime_s) selfObj._rotation_factor = selfObj._rotation_factor() end + ---add infotext selfObj.object:set_properties({ infotext = selfObj._arrow_name, }) - + ---idle animation if x_bows_registered_entity_def and x_bows_registered_entity_def._custom.animations.idle then selfObj.object:set_animation(unpack(x_bows_registered_entity_def._custom.animations.idle)) end + ---counter, e.g. for initial values set `on_step` + selfObj._step_count = 0 + ---Callbacks local on_after_activate_callback = x_bows_registered_arrow_def.custom.on_after_activate @@ -932,7 +928,7 @@ end ---Function receive a "luaentity" table as `self`. Called when the object dies. ---@param self XBows ----@param selfObj table +---@param selfObj EnityCustomAttrDef ---@param killer ObjectRef|nil ---@return nil function XBowsEntityDef.on_death(self, selfObj, killer) @@ -946,10 +942,20 @@ end --- Function receive a "luaentity" table as `self`. Called on every server tick, after movement and collision processing. `dtime`: elapsed time since last call. `moveresult`: table with collision info (only available if physical=true). ---@param self XBows ----@param selfObj table +---@param selfObj EnityCustomAttrDef ---@param dtime number ---@return nil function XBowsEntityDef.on_step(self, selfObj, dtime) + selfObj._step_count = selfObj._step_count + 1 + + if selfObj._step_count == 1 then + ---initialize + ---this has to be done here for raycast to kick-in asap + selfObj.object:set_velocity(vector.multiply(selfObj._player_look_dir, selfObj._strength)) + selfObj.object:set_acceleration({x = selfObj._acc_x, y = selfObj._acc_y, z = selfObj._acc_z}) + selfObj.object:set_yaw(minetest.dir_to_yaw(selfObj._player_look_dir)) + end + local pos = selfObj.object:get_pos() selfObj._old_pos = selfObj._old_pos or pos local ray = minetest.raycast(selfObj._old_pos, pos, true, true) @@ -1029,7 +1035,7 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) and pointed_thing.ref ~= selfObj.object and pointed_thing.ref:get_hp() > 0 and ( - (pointed_thing.ref:is_player() and pointed_thing.ref:get_player_name() ~= selfObj.user:get_player_name()) + (pointed_thing.ref:is_player() and pointed_thing.ref:get_player_name() ~= selfObj._user:get_player_name()) or ( pointed_thing.ref:get_luaentity() and pointed_thing.ref:get_luaentity().physical @@ -1041,25 +1047,16 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) then if pointed_thing.ref:is_player() then minetest.sound_play('x_bows_arrow_successful_hit', { - to_player = selfObj.user:get_player_name(), + to_player = selfObj._user:get_player_name(), gain = 0.3 }) else minetest.sound_play(selfObj._sound_hit, { - to_player = selfObj.user:get_player_name(), + to_player = selfObj._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 - selfObj.object:set_velocity({x = 0, y = 0, z = 0}) selfObj.object:set_acceleration({x = 0, y = 0, z = 0}) @@ -1136,7 +1133,6 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) -- 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 @@ -1145,10 +1141,6 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) 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 @@ -1156,10 +1148,6 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) 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 @@ -1167,10 +1155,6 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) 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 @@ -1178,10 +1162,6 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) 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 @@ -1189,10 +1169,6 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) 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 @@ -1200,10 +1176,6 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) 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 if not XBows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then @@ -1211,14 +1183,35 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) return end + ---normalize arrow scale when attached to scaled entity (prevents huge arrows when attached to scaled up entity models) + local obj_props = selfObj.object:get_properties() + local obj_to_props = pointed_thing.ref:get_properties() + local vs = vector.divide(obj_props.visual_size, obj_to_props.visual_size) + + selfObj.object:set_properties({visual_size = vs}) + -- attach arrow - selfObj.object:set_attach( - pointed_thing.ref, - '', - position, - rotation, - true + local position = vector.subtract( + ip_pos, + pointed_thing.ref:get_pos() ) + + if pointed_thing.ref:is_player() then + position = vector.multiply(position, 10) + end + + ---`after` here prevents visual glitch when the arrow still shows as huge for a split second + ---before the new calculated scale is applied + minetest.after(0, function() + selfObj.object:set_attach( + pointed_thing.ref, + '', + position, + rotation, + true + ) + end) + selfObj._attached = true selfObj._attached_to.type = pointed_thing.type selfObj._attached_to.pos = position @@ -1329,6 +1322,24 @@ function XBowsEntityDef.on_step(self, selfObj, dtime) on_hit_node_callback(selfObj, pointed_thing) end + local new_pos = selfObj.object:get_pos() + + minetest.add_particlespawner({ + amount = 5, + time = 0.25, + minpos = {x = new_pos.x - 0.4, y = new_pos.y + 0.2, z = new_pos.z - 0.4}, + maxpos = {x = new_pos.x + 0.4, y = new_pos.y + 0.3, z = new_pos.z + 0.4}, + minvel = {x = 0, y = 3, z = 0}, + maxvel = {x = 0, y = 4, z = 0}, + minacc = {x = 0, y = -28, z = 0}, + maxacc = {x = 0, y = -32, z = 0}, + minexptime = 1, + maxexptime = 1.5, + node = {name = node_def.name}, + collisiondetection = true, + object_collision = true, + }) + minetest.sound_play(selfObj._sound_hit, { pos = pointed_thing.under, gain = 0.6, @@ -1346,7 +1357,7 @@ end ---Function receive a "luaentity" table as `self`. Called when somebody punches the object. Note that you probably want to handle most punches using the automatic armor group system. Can return `true` to prevent the default damage mechanism. ---@param self XBows ----@param selfObj table +---@param selfObj EnityCustomAttrDef ---@param puncher ObjectRef|nil ---@param time_from_last_punch number|integer|nil ---@param tool_capabilities ToolCapabilitiesDef diff --git a/types/object.type.lua b/types/object.type.lua index 554face..693b40b 100644 --- a/types/object.type.lua +++ b/types/object.type.lua @@ -31,12 +31,19 @@ ---@field move_to fun(self: ObjectRef, pos: Vector, continuous?: boolean): nil Does an interpolated move for Lua entities for visually smooth transitions. If `continuous` is true, the Lua entity will not be moved to the current position before starting the interpolated move. For players this does the same as `set_pos`,`continuous` is ignored. ---@field set_hp fun(self: ObjectRef, hp: number, reason: table): nil set number of health points See reason in register_on_player_hpchange Is limited to the range of 0 ... 65535 (2^16 - 1) For players: HP are also limited by `hp_max` specified in object properties ---@field set_animation fun(self: ObjectRef, frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean): nil `frame_range`: table {x=num, y=num}, default: `{x=1, y=1}`, `frame_speed`: number, default: `15.0`, `frame_blend`: number, default: `0.0`, `frame_loop`: boolean, default: `true` +---@field get_velocity fun(self: ObjectRef): Vector returns the velocity, a vector. +---@field set_rotation fun(self: ObjectRef, rot: Vector): nil `rot` is a vector (radians). X is pitch (elevation), Y is yaw (heading) and Z is roll (bank). +---@field set_pos fun(self: ObjectRef, pos: Vector): nil + ---Moving things in the game are generally these. ---This is basically a reference to a C++ `ServerActiveObject`. ---@class ObjectRefLuaEntityRef ---@field set_velocity fun(self: ObjectRef, vel: Vector): nil `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}` ---@field remove fun(): nil remove object, The object is removed after returning from Lua. However the `ObjectRef` itself instantly becomes unusable with all further method calls having no effect and returning `nil`. +---@field get_rotation fun(self: ObjectRef): Vector returns the rotation, a vector (radians) +---@field get_attach fun(self: ObjectRef): any Returns parent, bone, position, rotation, forced_visible, or nil if it isn't attached. +---@field set_attach fun(self: ObjectRef, parent: ObjectRef, bone?: string, position?: Vector, rotation?: Vector, forced_visible?: boolean): any Returns parent, bone, position, rotation, forced_visible, or nil if it isn't attached. ---`ObjectRef` armor groups ---@class ObjectRefArmorGroups diff --git a/types/vector.type.lua b/types/vector.type.lua index 0b7d880..d73d308 100644 --- a/types/vector.type.lua +++ b/types/vector.type.lua @@ -13,3 +13,4 @@ ---@field round fun(v: Vector): Vector Returns a vector, each dimension rounded to nearest integer. At a multiple of 0.5, rounds away from zero. ---@field new fun(a, b?, c?): Vector Returns a new vector `(a, b, c)`. ---@field direction fun(p1: Vector, p2: Vector): Vector Returns a vector of length 1 with direction `p1` to `p2`. If `p1` and `p2` are identical, returns `(0, 0, 0)`. +---@field divide fun(v: Vector, s: Vector | number): Vector Returns a scaled vector. Deprecated: If `s` is a vector: Returns the Schur quotient. diff --git a/types/xbows.type.lua b/types/xbows.type.lua index 101f244..81ecbdc 100644 --- a/types/xbows.type.lua +++ b/types/xbows.type.lua @@ -154,3 +154,51 @@ ---@class EntityAnimationDef ---@field idle {frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean} ---@field on_hit_node {frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean} + +---Arrow object and custom attributes +---@class EnityCustomAttrDef +---@field object ObjectRef +---@field _velocity Vector +---@field _old_pos Vector +---@field _attached boolean +---@field _attached_to {["type"]: string, ["pos"]: Vector | nil} +---@field _has_particles boolean +---@field _lifetimer number +---@field _nodechecktimer number +---@field _is_drowning boolean +---@field _in_liquid boolean +---@field _shot_from_pos Vector +---@field _arrow_name string +---@field _bow_name string +---@field _user_name string +---@field _user ObjectRef +---@field _tflp number +---@field _tool_capabilities ToolCapabilitiesDef +---@field _is_critical_hit boolean +---@field _faster_arrows_multiplier number +---@field _add_damage number +---@field _caused_damage number +---@field _caused_knockback number +---@field _arrow_particle_effect string +---@field _arrow_particle_effect_crit string +---@field _arrow_particle_effect_fast string +---@field _sound_hit string +---@field _player_look_dir Vector +---@field _acc_x number +---@field _acc_y number +---@field _acc_z number +---@field _strength number +---@field name string +---@field _rotation_factor number | fun(): number +---@field _step_count number + +---Staticdata attributes +---@class EnityStaticDataAttrDef +---@field _arrow_name string +---@field _bow_name string +---@field _user_name string +---@field _is_critical_hit boolean +---@field _tool_capabilities ToolCapabilitiesDef +---@field _tflp number +---@field _add_damage number +---@field _faster_arrows_multiplier number | nil