diff --git a/ammo_api.lua b/ammo_api.lua
index 1f98753..2ba2756 100644
--- a/ammo_api.lua
+++ b/ammo_api.lua
@@ -205,7 +205,7 @@ function Guns4d.ammo.magazine(magname)
end
function Guns4d.ammo.magazine_of_gun(gunname, full, string)
- local gprops = Guns4d.gun.registered[gunname].properties
+ local gprops = Guns4d.gun._registered[gunname].properties
local magname = gprops.ammo.accepted_magazines[1]
assert(magname, "magazines are not accepted")
local mag = ItemStack(magname)
diff --git a/classes/Ammo_handler.lua b/classes/Ammo_handler.lua
index 23a60d2..07f168e 100644
--- a/classes/Ammo_handler.lua
+++ b/classes/Ammo_handler.lua
@@ -28,8 +28,10 @@ Ammo_handler = Instantiatable_class:inherit({
meta:set_int("guns4d_spawn_with_ammo", 0)
def:update_meta()
else
+ --create or reinitialize ammo data
if meta:get_string("guns4d_loaded_bullets") == "" then
- def.ammo.loaded_mag = gun.properties.ammo.initial_mag or "empty"
+ local ammo_props = gun.properties.ammo
+ def.ammo.loaded_mag = ammo_props.initial_mag or (ammo_props.accepted_magazines and ammo_props.accepted_magazines[1]) or "empty"
def.ammo.next_bullet = "empty"
def.ammo.total_bullets = 0
def.ammo.loaded_bullets = {}
diff --git a/classes/Gun.lua b/classes/Gun.lua
index 325b8a5..485ea44 100644
--- a/classes/Gun.lua
+++ b/classes/Gun.lua
@@ -1,119 +1,238 @@
--- Gun class
--- @classmod Gun
+--
+-- ## Defining a gun:
+--
+-- **method documentation coming soon** (or never...)
+--
+-- guns are defined by two table fields: their @{lvl1_fields.consts|consts} and their @{lvl1_fields.properties|properties}.
+-- @{lvl1_fields.properties|properties} define nearly everything, from how a gun handles to how it looks, what model it uses,
+-- while @{lvl1_fields.consts|consts} define attributes that should never change, like bones within the gun, framerates,
+-- wether the gun is allowed to have certain attributes at all. The rest is mainly for internal workings of the mod.
+--
+-- Guns4d uses a class system for most moving parts- including the gun. New guns therefor are created with the :inherit(def) method,
+-- where def is the definition of your new gun- or rather the changed parts of it. So to make a new gun you can run Guns4d.gun:inherit()
+-- or you can do the same thing with a seperate class of weapons. Set name to "__template" for template classes of guns.
+--
+-- @class Gun
+-- @compact
local Vec = vector
+
+--- gun fields
+--
+-- @table gun
+-- @field properties @{lvl1_fields.properties|properties} which define the vast majority of gun attributes and may change accross instances
+-- @field consts @{lvl1_fields.consts|constancts} which define gun attributes and should not be changed in an instance of the gun
+-- @field offsets @{lvl1_fields.offsets|properties} runtime storage of offsets generated by recoil sway wag or any other element.
+-- @compact
local gun_default = {
- --itemstack = Itemstack
- --gun_entity = ObjRef
+ --- `string` the name of the gun. Set to __template for guns which have no instances.
name = "__guns4d:default__",
+ --- `ItemStack` itemstack held by the player
+ itemstack = nil,
+ --- `ObjRef` the gun entity
+ gun_entity = nil,
+ --- `string` the itemstring of the gun- i.e. "guns4d_pack_1:m4". Set to "" for __template guns.
itemstring = "",
- registered = {},
- property_modifiers = {},
-
- --ldoc will fuck this up, so fields are handled by external post-generation script.
+ --- list of registered guns, **DO NOT MODIFY** I really need a metatable for this class...
+ _registered = {},
+ --- `bool` is the bolt charged
+ bolt_charged = false,
+ --- `table` list of particle spawner handles (generated by firing)
+ particle_spawners = {},
+ --- `int` the active index of the firemode from @{lvl1_fields.properties.firemodes}
+ current_firemode = 1,
+ --- `float` walking time used to generate the figure 8 for wag
+ walking_tick = 0,
+ --- `float`
+ time_since_last_fire = 0,
+ --- `float`
+ time_since_creation = 0,
+ --- `float` time left for the chamber to cycle (for firerates)
+ rechamber_time = 0,
+ --- `int` number of rounds left that need to be fired after a burst fire
+ burst_queue = 0,
+ --- `function`
+ muzzle_flash = Guns4d.effects.muzzle_flash,
--- properties
+ --
-- the table containing every attribute of the gun.
- -- @table properties
+ -- @table lvl1_fields.properties
+ -- @field hip `table` @{gun.properties.hip|hipfire properties}
+ -- @field ads `table` @{gun.properties.ads|aiming ("aiming down sights") properties}
+ -- @field firemodes `table` @{gun.properties.firemodes|list of firemodes}
+ -- @field firemode_inventory_overlays `table` @{gun.properties.firemode_inventory_overlays|list of corresponding images for firemodes}
+ -- @field recoil `table` @{gun.properties.recoil|defines the guns recoil}
+ -- @field sway `table` @{gun.properties.sway|defines the guns idle sway}
+ -- @field wag `table` @{gun.properties.wag|defines the movement of the gun while walking}
+ -- @field charging `table` @{gun.properties.charging|defines how rounds are chambered into the gun}
+ -- @field ammo @{gun.properties.ammo|defines what ammo the gun uses}
+ -- @field visuals @{gun.properties.visuals|defines visual attributes of the gun}
+ -- @compact
properties = {
- --%start "properties"
- infinite_inventory_overlay = "inventory_overlay_inf_ammo.png",
- breathing_scale = .5, --the max angluler offset caused by breathing.
- flash_offset = Vec.new(), --used by fire() (for fsx and ray start pos) [RENAME NEEDED]
- firerateRPM = 600, --used by update() and by extent fire() + default controls. The firerate of the gun. Rounds per minute
- burst = 3, --how many rounds in burst using when firemode is at "burst"
- ammo_handler = Ammo_handler,
+
+ --- `float`=.5 max angular deviation (vertical) from breathing
+ breathing_scale = .5,
+ --- `vector` the offset from the center of the muzzle flash. Used by fire()
+ flash_offset = Vec.new(),
+ --- `int`=600 The number of rounds (cartidges) this gun can throw per minute. Used by update(), fire() and default controls
+ firerateRPM = 600,
+ --- the item entity's attributes. This will later include held item definition...
item = {
collisionbox = ((not Guns4d.config.realistic_items) and {-.1,-.1,-.1, .1,.1,.1}) or {-.1,-.05,-.1, .1,.15,.1},
selectionbox = {-.1,-.1,-.1, .1,.1,.1}
},
- hip = {
+ --- properties.hip
+ -- @table gun.properties.hip
+ -- @compact
+ hip = {--#1
+ --- `vector` the offset of the gun (relative to the right arm's default position) at hip.
offset = Vec.new(),
+ --- the ratio that the look rotation is expressed through player_axial (rotated around the viewport) rotation as opposed to gun_axial (rotating the entity).
+ axis_rotation_ratio = .75,
+ --- sway speed multiplier while at hip
sway_vel_mul = 5,
+ --- sway angle multiplier while at hip+
sway_angle_mul = 1,
},
- ads = { --used by player_handler, animation handler (eye bone offset from horizontal_offset), gun entity (attached offset)
+ --- properties.ads
+ -- @table gun.properties.ads
+ -- @compact
+ ads = { --#2
+ --- `vector` the offset of the gun relative to the eye's position at hip.
offset = Vec.new(),
+ --- `float` the horizontal offset of the eye when aiming
horizontal_offset = 0,
+ --- the time it takes to go into full aim
aim_time = 1,
},
- firemodes = {
+ --- `int`=3 how many rounds in burst using when firemode is at "burst"
+ burst = 3,
+ --- properties.firemodes
+ --
+ -- list containing the firemodes of the gun. Default only contains "single". Strings allowed by default:
+ -- @table gun.properties.firemodes
+ -- @compact
+ -- @field "single"
+ -- @field "burst"
+ -- @field "auto"
+ firemodes = { --#3
"single", --not limited to semi-automatic.
- --"burst",
- --"auto"
},
- firemode_inventory_overlays = {
+ --- `string` overlay on the item to use when infinite ammo is on
+ infinite_inventory_overlay = "inventory_overlay_inf_ammo.png",
+ --- properties.firemode_inventory_overlays
+ --
+ -- defines the overlay on the gun item for each firemode. These are selected automatically by firemode string. Defaults are as follows:
+ -- @table gun.properties.firemode_inventory_overlays
+ -- @compact
+ firemode_inventory_overlays = { --#4
+ --- "inventory_overlay_single.png"
single = "inventory_overlay_single.png",
+ --- "inventory_overlay_auto.png"
auto = "inventory_overlay_auto.png",
+ --- "inventory_overlay_burst.png"
burst = "inventory_overlay_burst.png",
+ --- "inventory_overlay_safe.png" (unimplemented firemode)
safe = "inventory_overlay_safe.png"
},
- --[[bloom = { not yet implemented.
- base_aiming = 0, --amount of bloom at a full rest while aiming down sights (if possible)
- base_hip = 0, --amount of bloom at rest while not aiming down sights.
- recoil = {
- decay = 1, --decay rate
- amount = 0,
- ratio = 0, --ratio of x to y
- },
- walking = {
- decay = 1,
- amount = 0,
- ratio = 0,
- }
- },]]
- recoil = { --used by update_recoil()
+ --- properties.recoil
+ --
+ -- **IMPORTANT**: expects fields to be tables containing a "gun_axial" and "player_axial" field.
+ -- @example
+ -- property = {
+ -- gun_axial = type
+ -- player_axial = type
+ -- }
+ -- --using a vector...
+ -- property = {
+ -- gun_axial={x=float, y=float},
+ -- player_axial={x=float, y=float}
+ -- }`
+ -- @table gun.properties.recoil
+ -- @compact
+ recoil = { --#5 used by update_recoil()
+ --- `float` TL:DR higher decreases recoil at expense of smoothness. 1/value is the deviation of a normalized bell curve, where x is the time since firing.
+ -- this means that increasing it decreases the time it takes for the angular velocity to fully "decay".
velocity_correction_factor = { --velocity correction factor is currently very broken.
gun_axial = 1,
player_axial = 1,
},
+ --- `float` Correction of recoil offset per second is calculated as such: `target_correction_factor[axis]*time_since_fire*recoil[axis]`
target_correction_factor = { --angular correction rate per second: time_since_fire*target_correction_factor
gun_axial = 1,
player_axial = 1,
},
+ --- `float` The maximum rate per second of recoil offset as determined with @{target_correction_factor}
+ target_correction_max_rate = { --the cap for target_correction_fire (i.e. this is the maximum amount it will ever correct per second.)
+ gun_axial = math.huge,
+ player_axial = math.huge,
+ },
+ --- `float` caps the recoil velocity that can stack up from shots.
angular_velocity_max = { --max velocity, so your gun doesnt "spin me right round baby round round"
- gun_axial = 1,
- player_axial = 1,
+ gun_axial = 5,
+ player_axial = 5,
},
+ --- `vector` {x=`float`, y=`float`}, defines the initial angular velocity produced by firing the gun
angular_velocity = { --the velocity added per shot. This is the real "recoil" part of the recoil
gun_axial = {x=0, y=0},
player_axial = {x=0, y=0},
},
+ --- `vector` {x=`float`, y=`float`}, ranges -1 to 1. Defines the probability of the recoil being positive or negative for any given axis.
bias = { --dictates the distribution bias for the direction angular_velocity is in. I.e. if you want recoil to always go up you set x to 1, more often to the right? y to -.5
gun_axial = {x=1, y=0},
player_axial = {x=1, y=0},
},
- target_correction_max_rate = { --the cap for target_correction_fire (i.e. this is the maximum amount it will ever correct per second.)
- gun_axial = 1,
- player_axial = 1,
- },
+ --- `float` angular velocity multiplier when firing from the hip
hipfire_multiplier = { --the mutliplier for recoil (angular_velocity) at hipfire (can be fractional)
gun_axial = 1,
player_axial = 1
},
},
- sway = { --used by update_sway()
+ --- properties.sway
+ --
+ -- **IMPORTANT**: expects fields to be tables containing a "gun_axial" and "player_axial" field. In the same format as @{gun.properties.recoil}
+ -- @table gun.properties.sway
+ -- @compact
+ sway = { --#6
+ --- `float` maximum angle of the sway
max_angle = {
gun_axial = 0,
player_axial = 0,
},
+ --- `float` angular velocity the sway
angular_velocity = {
gun_axial = 0,
player_axial = 0,
},
+ --- `float` maximum angle multiplier while the gun is at the hip
hipfire_angle_multiplier = { --the mutliplier for sway max_angle at hipfire (can be fractional)
gun_axial = 2,
player_axial = 2
},
+ --- `float` velocity multiplier while the gun is at the hip
hipfire_velocity_multiplier = { --same as above but for velocity.
gun_axial = 2,
player_axial = 2
}
},
- walking_offset = { --used by update_walking() (or something)
- gun_axial = {x=1, y=-1},
- player_axial = {x=1,y=1},
+ --- properties.wag
+ --
+ -- @table gun.properties.wag
+ -- @compact
+ wag = {
+ --- `float`=1.6 the cycle speed multiplier
+ cycle_speed = 1.6,
+ --- `float`=1 decay factor when walking has stopped and offset remains.
+ decay_speed = 1,
+ --- `table` containing angular deviation while walking in the same format as @{gun.properties.recoil|an offset vector}. Acts as a multiplier on the figure-8 generated while walking.
+ offset = { --used by update_walking() (or something)
+ gun_axial = {x=1, y=-1},
+ player_axial = {x=1,y=1},
+ },
},
+ --- `table` containing a list of actions for PC users passed to @{Control_handler}
pc_control_actions = { --used by control_handler
__overfill=true, --this table will not be filled in.
aim = Guns4d.default_controls.aim,
@@ -122,6 +241,7 @@ local gun_default = {
on_use = Guns4d.default_controls.on_use,
firemode = Guns4d.default_controls.firemode
},
+ --- `table` containing a list of actions for touch screen users passed to @{Control_handler}
touch_control_actions = {
__overfill=true,
aim = Guns4d.default_touch_controls.aim,
@@ -130,53 +250,64 @@ local gun_default = {
on_secondary_use = Guns4d.default_touch_controls.on_secondary_use,
firemode = Guns4d.default_touch_controls.firemode
},
- charging = { --how the gun "cocks"
+ --- properties.charging
+ --
+ -- @table gun.properties.charging
+ -- @compact
+ charging = { --#7
+ --- `bool` defines wether the draw animation is played on swap (when loaded). Used in the instance construction method
require_draw_on_swap = true,
+ --- `string` "none" bolt will never need to be charged after reload, "catch" when fired to empty bolt will not need to be charged after reload, "no_catch" bolt will always need to be charged after reload.
bolt_charge_mode = "none", --"none"-chamber is always full, "catch"-when fired to dry bolt will not need to be charged after reload, "no_catch" bolt will always need to be charged after reload.
+ --- `float` the time it takes to swap to the gun
draw_time = 1,
+ --- `string` name of the animation to play from @{gun.properties.visuals.animations|visuals.animations}
draw_animation = "draw",
+ --- `string` name of the sound to play from @{gun.properties.sounds|sounds}
draw_sound = "draw"
--sound = soundspec
},
- reload = { --used by defualt controls. Still provides usefulness elsewhere.
+ --- and ordered list of reloading states used by @{default_controls}.
+ --
+ -- the default reload states for a magazine operated weapon, copied from the m4.
+ -- @example
+ -- {action="charge", time=.5, anim="charge", sounds={sound="ar_charge", delay = .2}},
+ -- {action="unload_mag", time=.25, anim="unload", sounds = {sound="ar_mag_unload"}},
+ -- {action="store", time=.5, anim="store", sounds = {sound="ar_mag_store"}},
+ -- {action="load", time=.5, anim="load", sounds = {sound="ar_mag_load", delay = .25}},
+ -- {action="charge", time=.5, anim="charge", sounds={sound="ar_charge", delay = .2}}
+ reload = {
__overfill=true,
- --{type="unload_mag", time=1, anim="unload_mag", interupt="to_ground", hold = true, sound = {sound = "load magazine", pitch = {min=.9, max=1.1}}},
- --{type="load", time=1, anim="load"}
},
- ammo = { --used by ammo_handler
+ --- properties.ammo
+ --
+ -- @table gun.properties.ammo
+ -- @compact
+ ammo = { --#8
magazine_only = false,
--capacity = 0, --this is only needed if magazine_only = false
+ --- `table` a list of accepted bullet itemstrings
accepted_bullets = {},
+ --- `table` a list of accepted magazine itemstrings
accepted_magazines = {},
- initial_mag = "empty"
+ --- `string` the mag the gun starts with. Set to "empty" for no mag, otherwise it defaults to accepted_magazines[1] (if present)
+ initial_mag = nil
},
+ --- properties.visuals
+ --
+ -- @table gun.properties.visuals
+ -- @compact
visuals = {
- --textures = {},
- --mesh="string.b3d",
+ --- toggles backface culling
backface_culling = true,
- root = "gun",
- magazine = "magazine",
- arm_right = "right_aimpoint",
- arm_left = "left_aimpoint",
+ --- a table of animations in the format {x=int, y=float}. Indexes define the name of the animation to be refrenced by other functions of the gun.
animations = { --used by animations handler for idle, and default controls
empty = {x=0,y=0},
loaded = {x=1,y=1},
},
},
+ --- a table of @{guns4d_soundspec|soundspecs} to be referenced by other functions
sounds = { --this does not contain reload sound effects.
- release_bolt = {
- __overfill=true,
- sound = "ar_release_bolt",
- max_hear_distance = 5,
- pitch = {
- min = .95,
- max = 1.05
- },
- gain = {
- min = .9,
- max = 1
- }
- },
fire = {
{
__overfill=true,
@@ -207,49 +338,55 @@ local gun_default = {
}
},
},
- --[[
- ammo = {
- accepted_magazines = {},
- accepted_bullets = {},
- magazine_only = false
- }
- ]]
+ ammo_handler = Ammo_handler,
+ sprite_scope = nil,
+ crosshair = nil,
initial_vertical_rotation = -60,
- --inventory_image
- --inventory_image_empty
- --used by ammo_handler
},
--- offsets
+ --
+ -- a list of tables each containing two vectors, a gun_axial offset and a player_axial offset. These are required for automatic initialization of offsets.
+ -- @example
+ -- recoil = {
+ -- gun_axial = {x=0, y=0}
+ -- player_axial = {x=0, y=0}
+ -- }
+ -- @table lvl1_fields.offsets
+ -- @compact
offsets = {
+ ---
recoil = {
gun_axial = Vec.new(),
player_axial = Vec.new(),
--move_dynamic_crosshair = false, this would make the dynamic crosshair move instead of get larger
},
+ ---
sway = {
gun_axial = Vec.new(),
player_axial = Vec.new(),
},
+ ---
walking = {
gun_axial = Vec.new(),
player_axial = Vec.new(),
tick = 0,
--velocity
},
+ ---
breathing = {
gun_axial = Vec.new(), --gun axial unimplemented...
player_axial = Vec.new(),
},
+ ---
look_snap = {
gun_axial = Vec.new(),
- player_axial = Vec.new() --unimplemented
+ player_axial = Vec.new()
},
},
- --[[spread = {
- recoil = vector.new(),
- walking = vector.new()
- },]]
+ --- `vector` containing the offset from animations, this will be generated if {@consts.ANIMATIONS_OFFSET_AIM}=true
animation_rotation = vector.new(),
+ --- total offsets of the gun in the same format as a @{offsets|an offset}
+ total_offset_rotation = nil,
--[[total_offset_rotation = { --can't be in offsets, as they're added automatically.
gun_axial = Vec.new(),
player_axial = Vec.new(),
@@ -260,52 +397,67 @@ local gun_default = {
gun_axial = Vec.new(),
player_axial = Vec.new(),
},
+ init_recoil = {
+ gun_axial = Vec.new(),
+ player_axial = Vec.new(),
+ },
sway = {
gun_axial = Vec.new(),
player_axial = Vec.new(),
},
},
- --magic number BEGONE. these are variables that are constant across the class, but are at times too specific to be config settings (generally), or otherwise must be overriden.
+ --- consts
+ --
+ -- These are variables that are constant across the class and should usually not ever be changed by instnaces
+ -- @table lvl1_fields.consts
+ -- @compact
consts = {
- HIP_ROTATION_RATIO = .75,
+ ---
AIM_OUT_AIM_IN_SPEED_RATIO = 2.5,
- KEYFRAME_SAMPLE_PRECISION = .1, --[[what frequency to take precalcualted keyframe samples. The lower this is the higher the memory allocation it will need- though minimal.
- This will fuck shit up if you change it after gun construction/inheritence (interpolation between precalculated vectors will not work right)]]
- WAG_CYCLE_SPEED = 1.6,
+ --- frequency of keyframe samples for animation offsets and
+ KEYFRAME_SAMPLE_PRECISION = .1,
+ --- default max hear distance when not specified
DEFAULT_MAX_HEAR_DISTANCE = 10,
+ --- `fps`=60 animation fps i.e. during firing when no length is specified
DEFAULT_FPS = 60,
- WAG_DECAY = 1, --divisions per second
+ --- `bool`
HAS_RECOIL = true,
+ --- `bool`
HAS_BREATHING = true,
+ --- `bool`
HAS_SWAY = true,
+ --- `bool`
HAS_WAG = true,
+ --- `bool` wether the gun rotates on it's own axis instead of the player's view (i.e. ironsight misalignments)
HAS_GUN_AXIAL_OFFSETS = true,
+ --- wether animations create an offset
ANIMATIONS_OFFSET_AIM = false,
+ --- whether the idle animation changes or not
LOOP_IDLE_ANIM = false,
+ --- general gain multiplier for third persons when hearing sounds
THIRD_PERSON_GAIN_MULTIPLIER = Guns4d.config.third_person_gain_multiplier,
- --ITEM_COLLISIONBOX = ((not Guns4d.config.realistic_items) and {-.1,-.1,-.1, .1,.1,.1}) or {-.1,-.05,-.1, .1,.15,.1},
- --ITEM_SELECTIONBOX = {-.2,-.2,-.2, .2,.2,.2},
+ --- the root bone of the gun (for animation offsets)
+ ROOT_BONE = "gun",
+ --- `string`="magazine",the bone of the magazine in the gun (for dropping mags)
+ MAG_BONE = "magazine",
+ --- `string`="right_aimpoint", the bone which the right arm aims at to
+ ARM_RIGHT_BONE = "right_aimpoint",
+ --- `string`="left_aimpoint", the bone which the left arm aims at to
+ ARM_LEFT_BONE = "left_aimpoint",
},
- --[[animation_data = { --where animations data is stored.
- anim_runtime = 0,
- length = 0,
- fps = 0,
- frames = {0,0},
- current_frame = 0,
- --[[animations = {
-
- }
- },]]
- bolt_charged = false,
- particle_spawners = {},
- current_firemode = 1,
- walking_tick = 0,
- time_since_last_fire = 0,
- time_since_creation = 0,
- rechamber_time = 0,
- burst_queue = 0,
- muzzle_flash = Guns4d.effects.muzzle_flash
}
+
+
+
+
+
+
+
+
+
+
+
+
--I dont remember why I made this, used it though lmao
function gun_default.multiplier_coefficient(multiplier, ratio)
return 1+((multiplier*ratio)-ratio)
@@ -430,36 +582,45 @@ function gun_default:attempt_fire()
if spent_bullet and spent_bullet ~= "empty" then
local dir = self.dir
local pos = self.pos
+ local props = self.properties
if not Guns4d.ammo.registered_bullets[spent_bullet] then
minetest.log("error", "unregistered bullet itemstring"..tostring(spent_bullet)..", could not fire gun (player:"..self.player:get_player_name()..")");
return false
end
+ --begin subtasks
local bullet_def = Guns4d.table.fill(Guns4d.ammo.registered_bullets[spent_bullet], {
player = self.player,
--we don't want it to be doing fuckshit and letting players shoot through walls.
- pos = pos-((self.handler.control_handler.ads and dir*self.properties.ads.offset.z) or dir*self.properties.hip.offset.z),
+ pos = pos-((self.handler.control_handler.ads and dir*props.ads.offset.z) or dir*props.hip.offset.z),
--dir = dir, this is now collected directly by calling get_dir so pellets and spread can be handled by the bullet_ray instance.
gun = self
})
Guns4d.bullet_ray:new(bullet_def)
- if self.properties.visuals.animations.fire then
- self:set_animation(self.properties.visuals.animations.fire, nil, false)
+ if props.visuals.animations.fire then
+ self:set_animation(props.visuals.animations.fire, nil, false)
end
self:recoil()
self:muzzle_flash()
-
+ --[[if props.durability.shot_per_wear then
+ self:damage()
+ end]]
--print(dump(self.properties.sounds.fire))
- local fire_sound = Guns4d.table.deep_copy(self.properties.sounds.fire) --important that we copy because play_sounds modifies it.
+ local fire_sound = Guns4d.table.deep_copy(props.sounds.fire) --important that we copy because play_sounds modifies it.
fire_sound.pos = self.pos
self:play_sounds(fire_sound)
- self.rechamber_time = 60/self.properties.firerateRPM
+ self.rechamber_time = 60/props.firerateRPM
return true
end
end
end
+--[[function gun_default:damage()
+ assert(self.instance, "attempt to call object method on a class")
+ self.itemstack:set_wear(self.itemstack:get_wear()-self.properties.durability.shot_per_wear)
+ self.player:set_wielded_item(self.itemstack)
+end]]
local function rand_sign(b)
b = b or .5
local int = 1
@@ -474,6 +635,14 @@ function gun_default:recoil()
recoil[i] = recoil[i] + (rprops.angular_velocity[axis][i]
*rand_sign((rprops.bias[axis][i]/2)+.5))
*self.multiplier_coefficient(rprops.hipfire_multiplier[axis], 1-self.handler.ads_location)
+ --set original velocity
+ self.velocities.init_recoil[axis][i] = recoil[i]
+ end
+ local length = math.sqrt(recoil.x^2+recoil.y^2)
+ if length > rprops.angular_velocity_max[axis] then
+ local co = rprops.angular_velocity_max[axis]*length
+ recoil.x = recoil.x*co
+ recoil.y = recoil.y*co
end
end
self.time_since_last_fire = 0
@@ -498,8 +667,8 @@ function gun_default:update_look_rotation(dt)
local gun_axial = self.offsets.look_snap.gun_axial
local offset = handler.look_rotation.x-player_rot.x
gun_axial.x = Guns4d.math.clamp(offset, 0, 15*(offset/math.abs(offset)))
- gun_axial.x = gun_axial.x+(pitch*(1-self.consts.HIP_ROTATION_RATIO))
- self.offsets.look_snap.player_axial.x = -pitch*(1-self.consts.HIP_ROTATION_RATIO)
+ gun_axial.x = gun_axial.x+(pitch*(1-self.properties.hip.axis_rotation_ratio))
+ self.offsets.look_snap.player_axial.x = -pitch*(1-self.properties.hip.axis_rotation_ratio)
else
self.offsets.look_snap.gun_axial.x = 0
self.offsets.look_snap.player_axial.x = 0
@@ -668,6 +837,7 @@ function gun_default:update_wag(dt)
local handler = self.handler
local wag = self.offsets.walking
local velocity = wag.velocity
+ local props = self.properties
local old_tick
if handler.walking then
velocity = self.player:get_velocity()
@@ -682,7 +852,7 @@ function gun_default:update_wag(dt)
end
end
local walking_offset = self.offsets.walking
- if velocity and (not handler.walking) and (math.ceil(old_tick/self.consts.WAG_CYCLE_SPEED)+.5 < (math.ceil(wag.tick/self.consts.WAG_CYCLE_SPEED))+.5) and (wag.tick > old_tick) then
+ if velocity and (not handler.walking) and (math.ceil(old_tick/props.wag.cycle_speed)+.5 < (math.ceil(wag.tick/props.wag.cycle_speed))+.5) and (wag.tick > old_tick) then
wag.velocity = nil
return
end
@@ -694,13 +864,13 @@ function gun_default:update_wag(dt)
multiplier = 2
end
--if the result is negative we know that it's flipped, and thus can be ended.
- local inp = (wag.tick/self.consts.WAG_CYCLE_SPEED)*math.pi*multiplier
+ local inp = (wag.tick/props.wag.cycle_speed)*math.pi*multiplier
--this is a mess, I think that 1.6 is the frequency of human steps or something
- walking_offset[axis][i] = math.sin(inp)*self.properties.walking_offset[axis][i]
+ walking_offset[axis][i] = math.sin(inp)*self.properties.wag.offset[axis][i]
else
local old_value = walking_offset[axis][i]
if math.abs(walking_offset[axis][i]) > .005 then
- local multiplier = 1/self.consts.WAG_DECAY
+ local multiplier = 1/props.wag.decay_speed
walking_offset[axis][i] = walking_offset[axis][i]-(walking_offset[axis][i]*multiplier*dt)
else
walking_offset[axis][i] = 0
@@ -712,6 +882,7 @@ function gun_default:update_wag(dt)
end
end
end
+local e = 2.7182818284590452353602874713527 --I don't know how to find it otherwise...
function gun_default:update_recoil(dt)
for axis, _ in pairs(self.offsets.recoil) do
for _, i in pairs({"x","y"}) do
@@ -719,33 +890,35 @@ function gun_default:update_recoil(dt)
local recoil_vel = Guns4d.math.clamp(self.velocities.recoil[axis][i],-self.properties.recoil.angular_velocity_max[axis],self.properties.recoil.angular_velocity_max[axis])
local old_recoil_vel = recoil_vel
recoil = recoil + recoil_vel
- if math.abs(recoil_vel) > 0.01 then
- --look, I know this doesn't really make sense, but this is the best I can do atm. I've looked for better and mroe intuitive methods, I cannot find them.
- --8-(8*(1-(8/100))
- --recoil_vel = recoil_vel-((recoil_vel-(recoil_vel/(1+self.properties.recoil.velocity_correction_factor[axis])))*dt*10)
- recoil_vel = recoil_vel * (recoil_vel/(recoil_vel/(self.properties.recoil.velocity_correction_factor[axis]*2))*dt)
+ --this is modelled off a geometric sequence where the Y incercept of the sequence is set to recoil_vel.
+ if math.abs(recoil_vel) > 0.001 then
+ local r = (10*self.properties.recoil.velocity_correction_factor[axis])^-1
+ local vel_co = e^-( (self.time_since_last_fire^2)/(2*r^2) )
+ recoil_vel = self.velocities.init_recoil[axis][i]*vel_co
else
recoil_vel = 0
end
- if math.abs(recoil_vel)>math.abs(old_recoil_vel) then
- recoil_vel = 0
- end
+ self.velocities.recoil[axis][i] = recoil_vel
+
--ax^2+bx+c
--recoil_velocity_correction_rate
--recoil_correction_rate
local old_recoil = recoil
- if math.abs(recoil) > 0.001 then
- local correction_multiplier = self.time_since_last_fire*self.properties.recoil.target_correction_factor[axis]
- local correction_value = recoil*correction_multiplier
- correction_value = Guns4d.math.clamp(math.abs(correction_value), 0, self.properties.recoil.target_correction_max_rate[axis])
- recoil=recoil-(correction_value*dt*(math.abs(recoil)/recoil))
+ local abs = math.abs(recoil)
+ local sign = old_recoil/abs
+ if abs > 0.001 then
+ local correction_value = abs*self.time_since_last_fire*self.properties.recoil.target_correction_factor[axis]
+ correction_value = Guns4d.math.clamp(correction_value, 0, self.properties.recoil.target_correction_max_rate[axis])
+ abs=abs-(correction_value*dt)
--prevent overcorrection
- if math.abs(recoil) > math.abs(old_recoil) then
- recoil = 0
+ if abs < 0 then
+ abs = 0
end
end
- self.velocities.recoil[axis][i] = recoil_vel
- self.offsets.recoil[axis][i] = recoil
+ if sign~=sign then
+ sign = 1
+ end
+ self.offsets.recoil[axis][i] = abs*sign
end
end
end
diff --git a/classes/Instantiatable_class.lua b/classes/Instantiatable_class.lua
index 4cde73f..67494c5 100644
--- a/classes/Instantiatable_class.lua
+++ b/classes/Instantiatable_class.lua
@@ -1,5 +1,5 @@
---- Instantiatable_class. The system for defining classes in 4dguns. Please note the capital "I", Ldoc converts it to a lowercase in all of this file
--- @classmod Instantiatable_class
+--- The system for defining classes in 4dguns. Please note the capital "I", Ldoc converts it to a lowercase in all of this file
+-- @class Instantiatable_class
Instantiatable_class = {
instance = false,
diff --git a/classes/Player_handler.lua b/classes/Player_handler.lua
index 1456df1..0683f28 100644
--- a/classes/Player_handler.lua
+++ b/classes/Player_handler.lua
@@ -173,7 +173,7 @@ end
function player_handler:is_holding_gun()
assert(self.instance, "attempt to call object method on a class")
if self.wielded_item then
- for name, obj in pairs(Guns4d.gun.registered) do
+ for name, obj in pairs(Guns4d.gun._registered) do
if obj.itemstring == self.wielded_item:get_name() then
return obj
end
diff --git a/classes/gun_construct.lua b/classes/gun_construct.lua
index d6289cd..2be69e7 100644
--- a/classes/gun_construct.lua
+++ b/classes/gun_construct.lua
@@ -157,9 +157,9 @@ local function initialize_b3d_animation_data(self, props)
}
--print(table.tostring(self.b3d_model))
--precalculate keyframe "samples" for intepolation.
- local left = mtul.b3d_nodes.get_node_by_name(self.b3d_model, props.visuals.arm_left, true)
- local right = mtul.b3d_nodes.get_node_by_name(self.b3d_model, props.visuals.arm_right, true)
- local main = mtul.b3d_nodes.get_node_by_name(self.b3d_model, props.visuals.root, true)
+ local left = mtul.b3d_nodes.get_node_by_name(self.b3d_model, self.consts.ARM_LEFT_BONE, true)
+ local right = mtul.b3d_nodes.get_node_by_name(self.b3d_model, self.consts.ARM_RIGHT_BONE, true)
+ local main = mtul.b3d_nodes.get_node_by_name(self.b3d_model, self.consts.ROOT_BONE, true)
--we add 2 because we have to add 1 for the loop to make it there if it's a float val, and MTUL uses a system where frame 0 is 1
for target_frame = 0, self.b3d_model.node.animation.frames+1, self.consts.KEYFRAME_SAMPLE_PRECISION do
--we need to check that the bone exists first.
@@ -261,6 +261,7 @@ function gun_default:construct_base_class()
self.consts = Guns4d.table.fill(self.parent_class.consts, self.consts or {})
props = self.properties
validate_controls(props)
+ assert((self.properties.recoil.velocity_correction_factor.gun_axial>=1) and (self.properties.recoil.velocity_correction_factor.player_axial>=1), "velocity correction must not be less than one.")
initialize_b3d_animation_data(self, props) --this is for animation offsets (like the spritescope uses)
@@ -278,6 +279,6 @@ function gun_default:construct_base_class()
self.accepted_magazines[v] = true
end
- Guns4d.gun.registered[self.name] = self --add gun self to the registered table
+ Guns4d.gun._registered[self.name] = self --add gun self to the registered table
register_visual_entity(self, props) --register the visual entity
end
\ No newline at end of file
diff --git a/docs/config.ld b/docs/config.ld
deleted file mode 100644
index 6bd9b01..0000000
--- a/docs/config.ld
+++ /dev/null
@@ -1,11 +0,0 @@
-project="4dguns"
-title="4dguns documentation"
-description="THEE ultimate 3d gun library."
-format="markdown"
-backtick_references=false
-file = {
- "../",
-}
-dir='./outputted documentation'
-readme='../README.md'
-style='!new'
\ No newline at end of file
diff --git a/docs/guns4d_doc_gen/config.guns4d b/docs/guns4d_doc_gen/config.guns4d
deleted file mode 100644
index 04c17dc..0000000
--- a/docs/guns4d_doc_gen/config.guns4d
+++ /dev/null
@@ -1,3 +0,0 @@
-guns4d_classes = {
- "Gun"
-}
\ No newline at end of file
diff --git a/docs/guns4d_doc_gen/init.lua b/docs/guns4d_doc_gen/init.lua
deleted file mode 100644
index 092b7bd..0000000
--- a/docs/guns4d_doc_gen/init.lua
+++ /dev/null
@@ -1,50 +0,0 @@
-local config = {}
-local ld_chunk = assert(loadfile("./config.ld"))
-setfenv(ld_chunk, config)
-ld_chunk()
-local guns4d_chunk = assert(loadfile("./config.guns4d"))
-setfenv(guns4d_chunk, config)
-guns4d_chunk()
-
-
-function trim_leading_space(line)
- local white_space = 0
- while string.sub(line, 1, 1) == " " do
- line = string.sub(line, 2)
- white_space = white_space+1
- end
- return line, white_space
-end
---there should be
-function generate_field_hyperlink_string(name)
- return ""
-end
-
-local field_tag = "
Fields:
"
-for _, class in pairs(config.guns4d_classes) do
-
- --read the file, break down into a modifiable structure.
- local fp = config.dir.."/classes/"..class..".html"
- local file_stream = io.open(fp, "r")
- assert(file_stream, "file not found while generating class docs, check class '"..class.."' is tagged as an @class")
- local line = 0
- local file = {}
- for line_text in file_stream:lines("*a") do
- line=line+1
- file[line]=line_text
- end
-
- --find fields and their associated class (with their hyperlink)
- for i, text in pairs(file) do
- --print(i,text)
- local trm_text, indent = trim_leading_space(text)
- if trm_text==field_tag then
- local line = i
- while
-
- do
-
- end
- end
- end
-end
\ No newline at end of file
diff --git a/docs/install_and_build_docs b/docs/install_and_build_docs
deleted file mode 100644
index cdf02a1..0000000
--- a/docs/install_and_build_docs
+++ /dev/null
@@ -1,13 +0,0 @@
-#! /bin/sh
-
-# on github, leafo/gh-actions-lua leafo/gh-actions-luarocks setup luarocks for us.
-#~ sudo apt-get install lua5.3 liblua5.3-dev luarocks
-
-# github ldoc is far ahead of the released version.
-echo ldoc version:
-git ls-remote https://github.com/lunarmodules/LDoc master
-luarocks --local install https://raw.githubusercontent.com/lunarmodules/LDoc/master/ldoc-scm-3.rockspec
-
-echo
-cd ./doc
-~/.luarocks/bin/ldoc .
diff --git a/docs/luadox/luadox b/docs/luadox/luadox
new file mode 100644
index 0000000..2a5ae53
Binary files /dev/null and b/docs/luadox/luadox differ
diff --git a/docs/luadox/luadox.conf b/docs/luadox/luadox.conf
new file mode 100644
index 0000000..6d89b2f
--- /dev/null
+++ b/docs/luadox/luadox.conf
@@ -0,0 +1,28 @@
+[project]
+# Project name that is displayed on the top bar of each page
+name = Guns4d | The ultimate 3d gun mod.
+# HTML title that is appended to every page. If not defined, name is used.
+title = Guns4d
+# A list of files or directories for LuaDox to parse. Globs are supported.
+# This can be spread across multiple lines if you want, as long as the
+# other lines are indented.
+files = ../../*.lua
+ ../../classes/*.lua
+# The directory containing the rendered output files, which will be created
+# if necessary.
+outdir = ../out
+# Path to a custom css file that will be included on every page. This will
+# be copied into the outdir.
+# css = custom.css
+# Path to a custom favicon. This will be copied into the outdir.
+# favicon = img/favicon.png
+# If require()d files discovered in source should also be parsed.
+follow = true
+
+[manual]
+# Custom manual pages in the form: id = filename.
+#
+# The ids must not conflict with any class or module name otherwise references
+# will not properly resolve.
+# index = intro.md
+# tutorial = tut.md
\ No newline at end of file
diff --git a/docs/luadox/run.bat b/docs/luadox/run.bat
new file mode 100644
index 0000000..82ef7b7
--- /dev/null
+++ b/docs/luadox/run.bat
@@ -0,0 +1,4 @@
+::literally just so I dont have to open powershell every time.
+::python 3.8+ required.
+@echo off
+python ./luadox -c ./luadox.conf
\ No newline at end of file
diff --git a/docs/out/class/Gun.html b/docs/out/class/Gun.html
new file mode 100644
index 0000000..35300c7
--- /dev/null
+++ b/docs/out/class/Gun.html
@@ -0,0 +1,780 @@
+
+
+
+
+
+ Gun - Guns4d
+
+
+
+
+
+
guns are defined by two table fields: their consts and their properties.
+properties define nearly everything, from how a gun handles to how it looks, what model it uses,
+while consts define attributes that should never change, like bones within the gun, framerates,
+wether the gun is allowed to have certain attributes at all. The rest is mainly for internal workings of the mod.
+
Guns4d uses a class system for most moving parts- including the gun. New guns therefor are created with the :inherit(def) method,
+where def is the definition of your new gun- or rather the changed parts of it. So to make a new gun you can run Guns4d.gun:inherit()
+or you can do the same thing with a seperate class of weapons. Set name to "__template" for template classes of guns.
the ratio that the look rotation is expressed through player_axial (rotated around the viewport) rotation as opposed to gun_axial (rotating the entity).
float TL:DR higher decreases recoil at expense of smoothness. 1/value is the deviation of a normalized bell curve, where x is the time since firing.
+this means that increasing it decreases the time it takes for the angular velocity to fully "decay".
string "none" bolt will never need to be charged after reload, "catch" when fired to empty bolt will not need to be charged after reload, "no_catch" bolt will always need to be charged after reload.
a list of tables each containing two vectors, a gun_axial offset and a player_axial offset. These are required for automatic initialization of offsets.
the class from which this class was inherited from
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/out/img/i-bitbucket.svg b/docs/out/img/i-bitbucket.svg
new file mode 100644
index 0000000..90ca0fe
--- /dev/null
+++ b/docs/out/img/i-bitbucket.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/out/img/i-download.svg b/docs/out/img/i-download.svg
new file mode 100644
index 0000000..67c9440
--- /dev/null
+++ b/docs/out/img/i-download.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/out/img/i-github.svg b/docs/out/img/i-github.svg
new file mode 100644
index 0000000..25d9004
--- /dev/null
+++ b/docs/out/img/i-github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/out/img/i-gitlab.svg b/docs/out/img/i-gitlab.svg
new file mode 100644
index 0000000..6da80a3
--- /dev/null
+++ b/docs/out/img/i-gitlab.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/out/img/i-left.svg b/docs/out/img/i-left.svg
new file mode 100644
index 0000000..72f5e6d
--- /dev/null
+++ b/docs/out/img/i-left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/out/img/i-right.svg b/docs/out/img/i-right.svg
new file mode 100644
index 0000000..22dc526
--- /dev/null
+++ b/docs/out/img/i-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/out/index.html b/docs/out/index.html
new file mode 100644
index 0000000..7e2945b
--- /dev/null
+++ b/docs/out/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+ Search - Guns4d
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/out/index.js b/docs/out/index.js
new file mode 100644
index 0000000..a550f32
--- /dev/null
+++ b/docs/out/index.js
@@ -0,0 +1,130 @@
+var docs = [
+{path:"class/Gun.html", type:"class", title:"Gun.Gun", text:"Gun class Defining a gun: *method documentation coming soon* (or never...) guns are defined by two table fields: their consts and their properties. properties define nearly everything, from how a gun handles to how it looks, what model it uses, while consts define attributes that should never change, like bones within the gun, framerates, wether the gun is allowed to have certain attributes at all. The rest is mainly for internal workings of the mod. Guns4d uses a class system for most moving parts- including the gun. New guns therefor are created with the :inherit(def) method, where def is the definition of your new gun- or rather the changed parts of it. So to make a new gun you can run Guns4d.gun:inherit() or you can do the same thing with a seperate class of weapons. Set name to \"__template\" for template classes of guns."},
+{path:"class/Instantiatable_class.html", type:"class", title:"Instantiatable_class.Instantiatable_class", text:"The system for defining classes in 4dguns. Please note the capital \"I\", Ldoc converts it to a lowercase in all of this file"},
+{path:"module/misc_helpers.html", type:"module", title:"misc_helpers", text:""},
+{path:"module/play_sound.html", type:"module", title:"play_sound", text:"implements tools for quickly playing audio."},
+{path:"module/Bullet_hole.html", type:"module", title:"Bullet_hole", text:""},
+{path:"module/Control_handler.html", type:"module", title:"Control_handler", text:""},
+{path:"module/Player_model_handler.html", type:"module", title:"Player_model_handler", text:""},
+{path:"module/play_sound.html#guns4d_soundspec.min_hear_distance", type:"field", title:"guns4d_soundspec.min_hear_distance", text:"float this is useful if you wish to play a sound which has a \"far\" sound, such as distant gunshots. incompatible with to_player"},
+{path:"module/play_sound.html#guns4d_soundspec.sounds", type:"field", title:"guns4d_soundspec.sounds", text:"table a weighted_randoms table for randomly selecting sounds. The output will overwrite the sound field."},
+{path:"module/play_sound.html#guns4d_soundspec.to_player", type:"field", title:"guns4d_soundspec.to_player", text:"objRef 4dguns changes to_player so it only plays positionless audio (as it is only intended for first person audio). If set to string \"from_player\" and player present"},
+{path:"module/play_sound.html#guns4d_soundspec.player", type:"field", title:"guns4d_soundspec.player", text:"objRef this is so to_player being set to \"from_player\". It's to be set to the player which fired the weapon."},
+{path:"module/play_sound.html#guns4d_soundspec.delay", type:"field", title:"guns4d_soundspec.delay", text:"float delays the playing of the sound"},
+{path:"module/play_sound.html#guns4d_soundspec.attenuation_rate", type:"field", title:"guns4d_soundspec.attenuation_rate", text:"float the rate of dropoff for a sound. I figure this is a bit more intuitive then jacking the gain up super high for every sound... Set the default in config."},
+{path:"module/play_sound.html#guns4d_soundspec.split_audio_by_perspective", type:"field", title:"guns4d_soundspec.split_audio_by_perspective", text:"bool [GUN CLASS SPECIFIC] tells the gun wether to split into third and first person (positionless) audio and adjust gain."},
+{path:"module/play_sound.html#guns4d_soundspec.third_person_gain_multiplier", type:"field", title:"guns4d_soundspec.third_person_gain_multiplier", text:"float [GUN CLASS SPECIFIC] replaces the constant/config value \"third_person_gain_multiplier/THIRD_PERSON_GAIN_MULTIPLIER\"."},
+{path:"class/Gun.html#gun.properties", type:"field", title:"gun.properties", text:"properties which define the vast majority of gun attributes and may change accross instances"},
+{path:"class/Gun.html#gun.consts", type:"field", title:"gun.consts", text:"constancts which define gun attributes and should not be changed in an instance of the gun"},
+{path:"class/Gun.html#gun.offsets", type:"field", title:"gun.offsets", text:"properties runtime storage of offsets generated by recoil sway wag or any other element."},
+{path:"class/Gun.html#gun.name", type:"field", title:"gun.name", text:"string the name of the gun. Set to __template for guns which have no instances."},
+{path:"class/Gun.html#gun.itemstack", type:"field", title:"gun.itemstack", text:"ItemStack itemstack held by the player"},
+{path:"class/Gun.html#gun.gun_entity", type:"field", title:"gun.gun_entity", text:"ObjRef the gun entity"},
+{path:"class/Gun.html#gun.itemstring", type:"field", title:"gun.itemstring", text:"string the itemstring of the gun- i.e. \"guns4d_pack_1:m4\". Set to \"\" for __template guns."},
+{path:"class/Gun.html#gun._registered", type:"field", title:"gun._registered", text:"list of registered guns, *DO NOT MODIFY* I really need a metatable for this class..."},
+{path:"class/Gun.html#gun.bolt_charged", type:"field", title:"gun.bolt_charged", text:"bool is the bolt charged"},
+{path:"class/Gun.html#gun.particle_spawners", type:"field", title:"gun.particle_spawners", text:"table list of particle spawner handles (generated by firing)"},
+{path:"class/Gun.html#gun.current_firemode", type:"field", title:"gun.current_firemode", text:"int the active index of the firemode from lvl1_fields.properties.firemodes"},
+{path:"class/Gun.html#gun.walking_tick", type:"field", title:"gun.walking_tick", text:"float walking time used to generate the figure 8 for wag"},
+{path:"class/Gun.html#gun.time_since_last_fire", type:"field", title:"gun.time_since_last_fire", text:"float"},
+{path:"class/Gun.html#gun.time_since_creation", type:"field", title:"gun.time_since_creation", text:"float"},
+{path:"class/Gun.html#gun.rechamber_time", type:"field", title:"gun.rechamber_time", text:"float time left for the chamber to cycle (for firerates)"},
+{path:"class/Gun.html#gun.burst_queue", type:"field", title:"gun.burst_queue", text:"int number of rounds left that need to be fired after a burst fire"},
+{path:"class/Gun.html#gun.muzzle_flash", type:"field", title:"gun.muzzle_flash", text:"function"},
+{path:"class/Gun.html#lvl1_fields.properties.hip", type:"field", title:"lvl1_fields.properties.hip", text:"table hipfire properties"},
+{path:"class/Gun.html#lvl1_fields.properties.ads", type:"field", title:"lvl1_fields.properties.ads", text:"table aiming (\"aiming down sights\") properties"},
+{path:"class/Gun.html#lvl1_fields.properties.firemodes", type:"field", title:"lvl1_fields.properties.firemodes", text:"table list of firemodes"},
+{path:"class/Gun.html#lvl1_fields.properties.firemode_inventory_overlays", type:"field", title:"lvl1_fields.properties.firemode_inventory_overlays", text:"table list of corresponding images for firemodes"},
+{path:"class/Gun.html#lvl1_fields.properties.recoil", type:"field", title:"lvl1_fields.properties.recoil", text:"table defines the guns recoil"},
+{path:"class/Gun.html#lvl1_fields.properties.sway", type:"field", title:"lvl1_fields.properties.sway", text:"table defines the guns idle sway"},
+{path:"class/Gun.html#lvl1_fields.properties.wag", type:"field", title:"lvl1_fields.properties.wag", text:"table defines the movement of the gun while walking"},
+{path:"class/Gun.html#lvl1_fields.properties.charging", type:"field", title:"lvl1_fields.properties.charging", text:"table defines how rounds are chambered into the gun"},
+{path:"class/Gun.html#lvl1_fields.properties.ammo", type:"field", title:"lvl1_fields.properties.ammo", text:"defines what ammo the gun uses"},
+{path:"class/Gun.html#lvl1_fields.properties.visuals", type:"field", title:"lvl1_fields.properties.visuals", text:"defines visual attributes of the gun"},
+{path:"class/Gun.html#lvl1_fields.properties.breathing_scale", type:"field", title:"lvl1_fields.properties.breathing_scale", text:"float=.5 max angular deviation (vertical) from breathing"},
+{path:"class/Gun.html#lvl1_fields.properties.flash_offset", type:"field", title:"lvl1_fields.properties.flash_offset", text:"vector the offset from the center of the muzzle flash. Used by fire()"},
+{path:"class/Gun.html#lvl1_fields.properties.firerateRPM", type:"field", title:"lvl1_fields.properties.firerateRPM", text:"int=600 The number of rounds (cartidges) this gun can throw per minute. Used by update(), fire() and default controls"},
+{path:"class/Gun.html#lvl1_fields.properties.item", type:"field", title:"lvl1_fields.properties.item", text:"the item entity's attributes. This will later include held item definition..."},
+{path:"class/Gun.html#gun.properties.hip.offset", type:"field", title:"gun.properties.hip.offset", text:"vector the offset of the gun (relative to the right arm's default position) at hip."},
+{path:"class/Gun.html#gun.properties.hip.axis_rotation_ratio", type:"field", title:"gun.properties.hip.axis_rotation_ratio", text:"the ratio that the look rotation is expressed through player_axial (rotated around the viewport) rotation as opposed to gun_axial (rotating the entity)."},
+{path:"class/Gun.html#gun.properties.hip.sway_vel_mul", type:"field", title:"gun.properties.hip.sway_vel_mul", text:"sway speed multiplier while at hip"},
+{path:"class/Gun.html#gun.properties.hip.sway_angle_mul", type:"field", title:"gun.properties.hip.sway_angle_mul", text:"sway angle multiplier while at hip+"},
+{path:"class/Gun.html#gun.properties.ads.offset", type:"field", title:"gun.properties.ads.offset", text:"vector the offset of the gun relative to the eye's position at hip."},
+{path:"class/Gun.html#gun.properties.ads.horizontal_offset", type:"field", title:"gun.properties.ads.horizontal_offset", text:"float the horizontal offset of the eye when aiming"},
+{path:"class/Gun.html#gun.properties.ads.aim_time", type:"field", title:"gun.properties.ads.aim_time", text:"the time it takes to go into full aim"},
+{path:"class/Gun.html#lvl1_fields.properties.burst", type:"field", title:"lvl1_fields.properties.burst", text:"int=3 how many rounds in burst using when firemode is at \"burst\""},
+{path:"class/Gun.html#gun.properties.firemodes."single"", type:"field", title:"gun.properties.firemodes.\"single\"", text:""},
+{path:"class/Gun.html#gun.properties.firemodes."burst"", type:"field", title:"gun.properties.firemodes.\"burst\"", text:""},
+{path:"class/Gun.html#gun.properties.firemodes."auto"", type:"field", title:"gun.properties.firemodes.\"auto\"", text:""},
+{path:"class/Gun.html#lvl1_fields.properties.infinite_inventory_overlay", type:"field", title:"lvl1_fields.properties.infinite_inventory_overlay", text:"string overlay on the item to use when infinite ammo is on"},
+{path:"class/Gun.html#gun.properties.firemode_inventory_overlays.single", type:"field", title:"gun.properties.firemode_inventory_overlays.single", text:"\"inventory_overlay_single.png\""},
+{path:"class/Gun.html#gun.properties.firemode_inventory_overlays.auto", type:"field", title:"gun.properties.firemode_inventory_overlays.auto", text:"\"inventory_overlay_auto.png\""},
+{path:"class/Gun.html#gun.properties.firemode_inventory_overlays.burst", type:"field", title:"gun.properties.firemode_inventory_overlays.burst", text:"\"inventory_overlay_burst.png\""},
+{path:"class/Gun.html#gun.properties.firemode_inventory_overlays.safe", type:"field", title:"gun.properties.firemode_inventory_overlays.safe", text:"\"inventory_overlay_safe.png\" (unimplemented firemode)"},
+{path:"class/Gun.html#gun.properties.recoil.velocity_correction_factor", type:"field", title:"gun.properties.recoil.velocity_correction_factor", text:"float TL:DR higher decreases recoil at expense of smoothness. 1/value is the deviation of a normalized bell curve, where x is the time since firing. this means that increasing it decreases the time it takes for the angular velocity to fully \"decay\"."},
+{path:"class/Gun.html#gun.properties.recoil.target_correction_factor", type:"field", title:"gun.properties.recoil.target_correction_factor", text:"float Correction of recoil offset per second is calculated as such: target_correction_factor[axis]time_since_firerecoil[axis]"},
+{path:"class/Gun.html#gun.properties.recoil.target_correction_max_rate", type:"field", title:"gun.properties.recoil.target_correction_max_rate", text:"float The maximum rate per second of recoil offset as determined with target_correction_factor"},
+{path:"class/Gun.html#gun.properties.recoil.angular_velocity_max", type:"field", title:"gun.properties.recoil.angular_velocity_max", text:"float caps the recoil velocity that can stack up from shots."},
+{path:"class/Gun.html#gun.properties.recoil.angular_velocity", type:"field", title:"gun.properties.recoil.angular_velocity", text:"vector {x=float, y=float}, defines the initial angular velocity produced by firing the gun"},
+{path:"class/Gun.html#gun.properties.recoil.bias", type:"field", title:"gun.properties.recoil.bias", text:"vector {x=float, y=float}, ranges -1 to 1. Defines the probability of the recoil being positive or negative for any given axis."},
+{path:"class/Gun.html#gun.properties.recoil.hipfire_multiplier", type:"field", title:"gun.properties.recoil.hipfire_multiplier", text:"float angular velocity multiplier when firing from the hip"},
+{path:"class/Gun.html#gun.properties.sway.max_angle", type:"field", title:"gun.properties.sway.max_angle", text:"float maximum angle of the sway"},
+{path:"class/Gun.html#gun.properties.sway.angular_velocity", type:"field", title:"gun.properties.sway.angular_velocity", text:"float angular velocity the sway"},
+{path:"class/Gun.html#gun.properties.sway.hipfire_angle_multiplier", type:"field", title:"gun.properties.sway.hipfire_angle_multiplier", text:"float maximum angle multiplier while the gun is at the hip"},
+{path:"class/Gun.html#gun.properties.sway.hipfire_velocity_multiplier", type:"field", title:"gun.properties.sway.hipfire_velocity_multiplier", text:"float velocity multiplier while the gun is at the hip"},
+{path:"class/Gun.html#gun.properties.wag.cycle_speed", type:"field", title:"gun.properties.wag.cycle_speed", text:"float=1.6 the cycle speed multiplier"},
+{path:"class/Gun.html#gun.properties.wag.decay_speed", type:"field", title:"gun.properties.wag.decay_speed", text:"float=1 decay factor when walking has stopped and offset remains."},
+{path:"class/Gun.html#gun.properties.wag.offset", type:"field", title:"gun.properties.wag.offset", text:"table containing angular deviation while walking in the same format as an offset vector. Acts as a multiplier on the figure-8 generated while walking."},
+{path:"class/Gun.html#lvl1_fields.properties.pc_control_actions", type:"field", title:"lvl1_fields.properties.pc_control_actions", text:"table containing a list of actions for PC users passed to Control_handler"},
+{path:"class/Gun.html#lvl1_fields.properties.touch_control_actions", type:"field", title:"lvl1_fields.properties.touch_control_actions", text:"table containing a list of actions for touch screen users passed to Control_handler"},
+{path:"class/Gun.html#gun.properties.charging.require_draw_on_swap", type:"field", title:"gun.properties.charging.require_draw_on_swap", text:"bool defines wether the draw animation is played on swap (when loaded). Used in the instance construction method"},
+{path:"class/Gun.html#gun.properties.charging.bolt_charge_mode", type:"field", title:"gun.properties.charging.bolt_charge_mode", text:"string \"none\" bolt will never need to be charged after reload, \"catch\" when fired to empty bolt will not need to be charged after reload, \"no_catch\" bolt will always need to be charged after reload."},
+{path:"class/Gun.html#gun.properties.charging.draw_time", type:"field", title:"gun.properties.charging.draw_time", text:"float the time it takes to swap to the gun"},
+{path:"class/Gun.html#gun.properties.charging.draw_animation", type:"field", title:"gun.properties.charging.draw_animation", text:"string name of the animation to play from visuals.animations"},
+{path:"class/Gun.html#gun.properties.charging.draw_sound", type:"field", title:"gun.properties.charging.draw_sound", text:"string name of the sound to play from sounds"},
+{path:"class/Gun.html#lvl1_fields.properties.reload", type:"field", title:"lvl1_fields.properties.reload", text:"and ordered list of reloading states used by default_controls. the default reload states for a magazine operated weapon, copied from the m4. Example"},
+{path:"class/Gun.html#gun.properties.ammo.accepted_bullets", type:"field", title:"gun.properties.ammo.accepted_bullets", text:"table a list of accepted bullet itemstrings"},
+{path:"class/Gun.html#gun.properties.ammo.accepted_magazines", type:"field", title:"gun.properties.ammo.accepted_magazines", text:"table a list of accepted magazine itemstrings"},
+{path:"class/Gun.html#gun.properties.ammo.initial_mag", type:"field", title:"gun.properties.ammo.initial_mag", text:"string the mag the gun starts with. Set to \"empty\" for no mag, otherwise it defaults to accepted_magazines[1] (if present)"},
+{path:"class/Gun.html#gun.properties.visuals.backface_culling", type:"field", title:"gun.properties.visuals.backface_culling", text:"toggles backface culling"},
+{path:"class/Gun.html#gun.properties.visuals.animations", type:"field", title:"gun.properties.visuals.animations", text:"a table of animations in the format {x=int, y=float}. Indexes define the name of the animation to be refrenced by other functions of the gun."},
+{path:"class/Gun.html#lvl1_fields.properties.sounds", type:"field", title:"lvl1_fields.properties.sounds", text:"a table of soundspecs to be referenced by other functions"},
+{path:"class/Gun.html#lvl1_fields.offsets.recoil", type:"field", title:"lvl1_fields.offsets.recoil", text:""},
+{path:"class/Gun.html#lvl1_fields.offsets.sway", type:"field", title:"lvl1_fields.offsets.sway", text:""},
+{path:"class/Gun.html#lvl1_fields.offsets.walking", type:"field", title:"lvl1_fields.offsets.walking", text:""},
+{path:"class/Gun.html#lvl1_fields.offsets.breathing", type:"field", title:"lvl1_fields.offsets.breathing", text:""},
+{path:"class/Gun.html#lvl1_fields.offsets.look_snap", type:"field", title:"lvl1_fields.offsets.look_snap", text:""},
+{path:"class/Gun.html#gun.animation_rotation", type:"field", title:"gun.animation_rotation", text:"vector containing the offset from animations, this will be generated if {@consts.ANIMATIONS_OFFSET_AIM}=true"},
+{path:"class/Gun.html#gun.total_offset_rotation", type:"field", title:"gun.total_offset_rotation", text:"total offsets of the gun in the same format as a an offset"},
+{path:"class/Gun.html#lvl1_fields.consts.AIM_OUT_AIM_IN_SPEED_RATIO", type:"field", title:"lvl1_fields.consts.AIM_OUT_AIM_IN_SPEED_RATIO", text:""},
+{path:"class/Gun.html#lvl1_fields.consts.KEYFRAME_SAMPLE_PRECISION", type:"field", title:"lvl1_fields.consts.KEYFRAME_SAMPLE_PRECISION", text:"frequency of keyframe samples for animation offsets and"},
+{path:"class/Gun.html#lvl1_fields.consts.DEFAULT_MAX_HEAR_DISTANCE", type:"field", title:"lvl1_fields.consts.DEFAULT_MAX_HEAR_DISTANCE", text:"default max hear distance when not specified"},
+{path:"class/Gun.html#lvl1_fields.consts.DEFAULT_FPS", type:"field", title:"lvl1_fields.consts.DEFAULT_FPS", text:"fps=60 animation fps i.e. during firing when no length is specified"},
+{path:"class/Gun.html#lvl1_fields.consts.HAS_RECOIL", type:"field", title:"lvl1_fields.consts.HAS_RECOIL", text:"bool"},
+{path:"class/Gun.html#lvl1_fields.consts.HAS_BREATHING", type:"field", title:"lvl1_fields.consts.HAS_BREATHING", text:"bool"},
+{path:"class/Gun.html#lvl1_fields.consts.HAS_SWAY", type:"field", title:"lvl1_fields.consts.HAS_SWAY", text:"bool"},
+{path:"class/Gun.html#lvl1_fields.consts.HAS_WAG", type:"field", title:"lvl1_fields.consts.HAS_WAG", text:"bool"},
+{path:"class/Gun.html#lvl1_fields.consts.HAS_GUN_AXIAL_OFFSETS", type:"field", title:"lvl1_fields.consts.HAS_GUN_AXIAL_OFFSETS", text:"bool wether the gun rotates on it's own axis instead of the player's view (i.e. ironsight misalignments)"},
+{path:"class/Gun.html#lvl1_fields.consts.ANIMATIONS_OFFSET_AIM", type:"field", title:"lvl1_fields.consts.ANIMATIONS_OFFSET_AIM", text:"wether animations create an offset"},
+{path:"class/Gun.html#lvl1_fields.consts.LOOP_IDLE_ANIM", type:"field", title:"lvl1_fields.consts.LOOP_IDLE_ANIM", text:"whether the idle animation changes or not"},
+{path:"class/Gun.html#lvl1_fields.consts.THIRD_PERSON_GAIN_MULTIPLIER", type:"field", title:"lvl1_fields.consts.THIRD_PERSON_GAIN_MULTIPLIER", text:"general gain multiplier for third persons when hearing sounds"},
+{path:"class/Gun.html#lvl1_fields.consts.ROOT_BONE", type:"field", title:"lvl1_fields.consts.ROOT_BONE", text:"the root bone of the gun (for animation offsets)"},
+{path:"class/Gun.html#lvl1_fields.consts.MAG_BONE", type:"field", title:"lvl1_fields.consts.MAG_BONE", text:"string=\"magazine\",the bone of the magazine in the gun (for dropping mags)"},
+{path:"class/Gun.html#lvl1_fields.consts.ARM_RIGHT_BONE", type:"field", title:"lvl1_fields.consts.ARM_RIGHT_BONE", text:"string=\"right_aimpoint\", the bone which the right arm aims at to"},
+{path:"class/Gun.html#lvl1_fields.consts.ARM_LEFT_BONE", type:"field", title:"lvl1_fields.consts.ARM_LEFT_BONE", text:"string=\"left_aimpoint\", the bone which the left arm aims at to"},
+{path:"class/Instantiatable_class.html#god_work.instance", type:"field", title:"god_work.instance", text:"defines wether the object is an instance"},
+{path:"class/Instantiatable_class.html#god_work.base_class", type:"field", title:"god_work.base_class", text:"only present for instances: the class from which this instance originates"},
+{path:"class/Instantiatable_class.html#god_work.parent_class", type:"field", title:"god_work.parent_class", text:"the class from which this class was inherited from"},
+{path:"module/misc_helpers.html#Guns4d.math.weighted_randoms", type:"function", title:"Guns4d.math.weighted_randoms", text:"picks a random index, with odds based on it's value. Returns the index of the selected."},
+{path:"module/play_sound.html#Guns4d.play_sounds", type:"function", title:"Guns4d.play_sounds", text:"allows you to play one or more sounds with more complex features, so sounds can be easily coded for guns without the need for functions. WARNING: this function modifies the tables passed to it, use Guns4d.table.shallow_copy() or table.copy for inputted soundspecs Example"},
+{path:"module/play_sound.html#Guns4d.get_sounds", type:"function", title:"Guns4d.get_sounds", text:"gets a list of currently playing Minetest sound handles from the Guns4d sound handle. Modification of table highly discouraged."},
+{path:"module/play_sound.html#Guns4d.stop_sounds", type:"function", title:"Guns4d.stop_sounds", text:"stops a list of sounds"},
+{path:"module/Bullet_hole.html#Bullet_hole.construct", type:"function", title:"Bullet_hole.construct", text:""},
+{path:"module/Control_handler.html#controls.toggle_touchscreen_mode", type:"function", title:"controls:toggle_touchscreen_mode", text:""},
+{path:"class/Instantiatable_class.html#Instantiatable_class.inherit", type:"function", title:"Instantiatable_class:inherit", text:"creates a new base class. Calls all constructors in the chain with def.instance=true"},
+{path:"class/Instantiatable_class.html#Instantiatable_class.new", type:"function", title:"Instantiatable_class:new", text:"creates an instance of the base class. Calls all constructors in the chain with def.instance=true"},
+{path:"module/Player_model_handler.html#player_model.construct", type:"function", title:"player_model.construct", text:""},
+{path:"module/misc_helpers.html#math", type:"section", title:"math helpers", text:"in guns4d.math"},
+{path:"module/misc_helpers.html#table", type:"section", title:"table helpers", text:"in guns4d.table"},
+{path:"module/misc_helpers.html#other", type:"section", title:"other helpers", text:""},
+];
\ No newline at end of file
diff --git a/docs/out/js-search.min.js b/docs/out/js-search.min.js
new file mode 100644
index 0000000..4adc065
--- /dev/null
+++ b/docs/out/js-search.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).JsSearch={})}(this,(function(e){"use strict";var t=function(){function e(){}return e.prototype.expandToken=function(e){for(var t,n=[],i=0,r=e.length;i=0&&(f=this._wrapText(f),e=e.substring(0,l)+f+e.substring(r+1),r+=n,p+=n)}return e},t._wrapText=function(e){var t=this._wrapperTagName;return"<"+t+">"+e+""+t+">"},e}();e.AllSubstringsIndexStrategy=t,e.CaseSensitiveSanitizer=r,e.ExactWordIndexStrategy=n,e.LowerCaseSanitizer=o,e.PrefixIndexStrategy=i,e.Search=_,e.SimpleTokenizer=h,e.StemmingTokenizer=f,e.StopWordsMap=d,e.StopWordsTokenizer=l,e.TfIdfSearchIndex=s,e.TokenHighlighter=m,e.UnorderedSearchIndex=u,Object.defineProperty(e,"__esModule",{value:!0})}));
diff --git a/docs/out/luadox.css b/docs/out/luadox.css
new file mode 100644
index 0000000..56bedd4
--- /dev/null
+++ b/docs/out/luadox.css
@@ -0,0 +1,629 @@
+:target {
+ background-color: #ffe080 !important;
+ color: black !important;
+ border-bottom: 1px solid #cca940 !important;
+ border-top: 1px solid #cca940 !important;
+}
+
+body {
+ font-family: sans-serif;
+ color: black;
+ padding: 0px;
+ margin: 0;
+}
+
+
+div.topbar {
+ box-sizing: border-box;
+ background-color: #465158;
+ position: fixed;
+ display: flex;
+ align-items: center;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+ height: 40px;
+ padding: 0 1em 0 0.4em;
+ z-index: 10;
+ font-size: 90%;
+ color: white;
+ box-shadow: 0 0px 15px 5px rgba(0, 0, 0, 0.4);
+}
+
+div.topbar div.group {
+ display: flex;
+ flex: 1;
+}
+
+div.topbar div.group.one {
+ justify-content: flex-start;
+}
+
+div.topbar div.group.two {
+ justify-content: center;
+}
+div.topbar div.group.three {
+ justify-content: flex-end;
+}
+
+div.topbar a {
+ color: white;
+ border: none;
+ padding: 0.5em 1em;
+}
+
+div.topbar div.description a {
+ font-size: 100% !important;
+ font-weight: normal !important;
+ margin-left: 0 !important;
+}
+
+div.topbar div.button a {
+ margin: 0.375em 0;
+ font-size: 80%;
+ font-weight: bold;
+ white-space: nowrap;
+ display: flex;
+ align-items: center;
+}
+
+div.topbar div.button.solid a {
+ margin-left: 1em;
+}
+
+div.topbar div.button.solid a,
+div.topbar div.button a:hover {
+ background-color: #697983;
+ text-shadow: 0 1px rgba(0, 0, 0, 0.5);
+ border-radius: 3px;
+ border: none;
+}
+div.topbar div.button.solid a:hover {
+ background-color: #8aa0ad;
+}
+
+div.topbar div.button a img {
+ width: 1.5em;
+}
+
+div.topbar div.button a img[src*='.svg'] {
+ filter: invert(1);
+}
+
+div.topbar div.button.iconleft a {
+ padding-left: 0.6em;
+ padding-right: 1em;
+}
+
+div.topbar div.button.iconright a {
+ padding-left: 1em;
+ padding-right: 0.6em;
+}
+
+div.topbar div.button.iconleft a img {
+ padding-right: 0.5em;
+}
+div.topbar div.button.iconright a img {
+ padding-left: 0.5em;
+}
+
+div.sidebar {
+ clear: both;
+ box-sizing: border-box;
+ background-color: #e8ecef;
+ width: 15em;
+ height: calc(100% - 40px);
+ padding: 1em 0.5em;
+ border-right: 1px solid #ccc;
+ position: fixed;
+ overflow-y: auto;
+}
+
+
+div.body {
+ margin-left: 15em;
+ margin-top: 2.5em;
+}
+
+div.sidebar div.heading {
+ font-weight: bold;
+}
+
+div.sidebar ul {
+ list-style-type: none;
+ padding-left: 1em;
+ margin-top: 0.5em;
+}
+div.sidebar li {
+ padding: 2px 0;
+}
+
+div.sidebar li.selected {
+ font-weight: bold;
+}
+
+div.sidebar div.sections li {
+ padding-left: 1em;
+ text-indent: -1em;
+}
+
+div.sidebar p,
+h2 p {
+ margin: 0;
+ padding: 0;
+ display: inline;
+}
+
+a {
+ color: #444;
+ text-decoration: none;
+ border-bottom: 1px solid #ccc;
+}
+
+a:hover {
+ color: #000;
+ border-bottom: 1px solid #666;
+}
+
+div.section, div.body div.manual {
+ padding: 0em 1em 1em 1em;
+}
+
+div.body pre {
+ margin: 0 2em;
+}
+
+div.manual h1 {
+ margin: 0 -16px 0 -20px;
+ padding: 10px 10px 10px 20px;
+ font-size: 120%;
+}
+
+div.section h2 {
+ margin: 0 -16px 0 -20px;
+ padding: 10px 10px 10px 20px;
+ background-color: #eeeeee;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ font-size: 120%;
+}
+
+div.manual h2,
+div.manual h3 {
+ margin: 1em -16px 0 -20px;
+ padding: 10px 10px 10px 20px;
+}
+
+div.manual h2 {
+ background-color: #eeeeee;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ font-size: 120%;
+ padding: 10px 10px 10px 20px;
+}
+
+div.manual h3 {
+ font-size: 110%;
+ padding: 5px 5px 5px 20px;
+ /* Ensures when :target applies (which adds borders) we don't affect layout */
+ border-top: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+}
+
+div.hierarchy {
+ margin-top: 2em;
+ margin-bottom: 2em;
+}
+
+div.hierarchy div.heading {
+ font-weight: bold;
+ margin-bottom: 0.5em;
+}
+div.hierarchy ul {
+ margin-top: 0.5em;
+ padding-left: 1em;
+}
+
+div.hierarchy li {
+ list-style-type: none;
+ margin: 0.4em 0;
+}
+div.hierarchy li span {
+ padding: 0.2em;
+ font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace;
+ font-size: 0.95em;
+ letter-spacing: 0.01em;
+}
+div.hierarchy li span em {
+ font-style: normal;
+}
+
+div.hierarchy li.self span {
+ background-color: #fbedc3;
+}
+
+h1,
+div.section h2.class,
+div.section h2.module {
+ color: white;
+ background-color: #555;
+ border-top: 1px solid #222;
+ border-bottom: 1px solid #222;
+ font-size: 140%;
+}
+
+
+code {
+ font-family: 'Courier New', monospace;
+ font-size: 0.95em;
+}
+
+var, a code {
+ letter-spacing: 0.01em;
+ font-weight: bold;
+ font-style: normal;
+ color: #444;
+}
+
+div.see::before {
+ content: "👉 ";
+}
+
+h3.fields, h3.functions {
+ padding-top: 1em;
+ padding-bottom: 0.5em !important;
+}
+dl p:first-child {
+ margin-top: 0.5em;
+}
+dl dd:not(:last-child),
+dl dd:not(:last-child) {
+ padding-bottom: 1.5em;
+ /*border-bottom: 1px solid #ddd;*/
+}
+
+
+dt {
+ padding: 0.5em;
+ margin-left: -1em;
+ margin-right: -1em;
+ /* Ensures when :target applies (which adds borders) we don't affect layout */
+ border-top: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+}
+dt var {
+ font-size: 1.15em;
+}
+dd {
+ margin-left: 2.5em;
+}
+
+dl.functions div.heading {
+ margin-top: 1em;
+ margin-left: 0em;
+ margin-bottom: 0.5em;
+ font-style: italic;
+}
+
+
+table.parameters,
+table.returns {
+ max-width: 90%;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ border-left: transparent;
+ border-right: transparent;
+ margin-left: 1em;
+ border-collapse: collapse;
+}
+table.parameters td,
+table.returns td {
+ padding: 0.4em 0;
+ vertical-align: top;
+}
+
+table.parameters tr:not(:last-child) td,
+table.returns tr:not(:last-child) td {
+ border-bottom: 1px solid #ccc;
+}
+
+table.parameters tr,
+table.returns tr {
+ background: #f7f7f7;
+}
+
+
+table.parameters tr td:first-child,
+table.returns tr td:first-child {
+ text-align: right;
+ padding-left: 1em;
+}
+
+table.parameters tr td:last-child,
+table.returns tr td:last-child {
+ padding-right: 1em;
+}
+
+table.parameters td.name,
+table.parameters td.types,
+table.returns td.name,
+table.returns td.types {
+ white-space: nowrap;
+ padding-right: 1em !important;
+}
+
+
+* {
+ scroll-padding-top: 40px;
+}
+
+div.section div.inner {
+ min-width: 20em;
+ max-width: 80em;
+}
+
+div.synopsis h3 {
+ display: none;
+}
+
+div.synopsis div.heading {
+ font-weight: bold;
+ margin-top: 1em;
+ margin-bottom: 0em;
+ margin-left: 0.5em;
+}
+
+div.synopsis table {
+ border-top: 1px solid #d2b089;
+ border-bottom: 1px solid #d2b089;
+ background-color: #f8f0e6;
+ width: 95%;
+ margin: 1em auto;
+ border-collapse: collapse;
+}
+div.synopsis td {
+ padding: 0.3em 0.5em 0.3em 0.5em;
+ vertical-align: top;
+}
+div.synopsis tr:not(:last-child) td {
+ border-bottom: 1px solid #e2d0ba;
+}
+
+div.synopsis td:first-child {
+ /* min-width: 15em;
+ max-width: 20em; */
+ white-space: nowrap;
+ padding-right: 1em;
+}
+
+div.synopsis td.meta,
+div.synopsis td.meta a {
+ white-space: nowrap;
+ color: #777777;
+}
+
+div.synopsis td > p,
+table.parameters td > p,
+table.returns td > p {
+ margin: 0;
+}
+
+div.synopsis td > p:not(:first-child) {
+ margin-top: 0.5em;
+}
+
+div.synopsis td a.permalink {
+ color: #aaa;
+}
+
+
+div.admonition {
+ border: 1px solid #609060;
+ background-color: #e9ffe9;
+ width: 90%;
+ margin: 1.5em auto;
+}
+
+div.admonition div.title {
+ margin: 0;
+ margin-top: 0px;
+ padding: 0.3em 0 0.3em 0.5em;
+ color: white;
+ font-weight: bold;
+ font-size: 1.0em;
+ text-shadow: 0 1px rgba(0, 0, 0, 0.5);
+}
+
+div.admonition div.body {
+ margin: 0.5em 1em 0.5em 1em;
+ padding: 0;
+}
+
+
+div.warning {
+ border: 1px solid #900000;
+ background-color: #ffe9e9;
+}
+
+div.warning div.title {
+ background-color: #b04040;
+ border-bottom: 1px solid #900000;
+}
+
+div.note div.title {
+ background-color: #70A070;
+ border-bottom: 1px solid #609060;
+}
+
+dl.fields dt span.icon::after {
+ content: "🏷️ ";
+ vertical-align: middle;
+ font-size: 110%;
+}
+
+dl.fields dt span.tag,
+dl.functions dt span.tag {
+ border-radius: 20px;
+ border: 1px solid #ccc;
+ background-color: #eee;
+ display: inline;
+ opacity: 0.6;
+ padding: 5px 10px;
+ font-size: 80%;
+ margin: 0 0.5em;
+}
+
+dl.fields dt span.tag:first-of-type,
+dl.functions dt span.tag:first-of-type {
+ margin-left: 2em;
+}
+
+dl.fields dt span.meta::before,
+dl.functions dt span.meta::before {
+ filter: saturate(0);
+ opacity: 0.9;
+ content: "👁️";
+ padding-right: 0.5em;
+}
+
+dl.fields dt span.type::before {
+ filter: saturate(0);
+ opacity: 0.9;
+ content: "✏️";
+ padding-right: 0.5em;
+}
+
+dl.functions dt span.icon::after {
+ content: "🏃♂️ ";
+ vertical-align: middle;
+ font-size: 140%;
+}
+
+a.permalink:hover {
+ color: #c60f0f !important;
+ border: none;
+}
+
+a.permalink {
+ color: #ccc;
+ font-size: 1em;
+ margin-left: 6px;
+ padding: 0 4px 0 4px;
+ text-decoration: none;
+ border: none;
+ visibility: hidden;
+}
+
+h1:hover > a.permalink,
+h2:hover > a.permalink,
+h3:hover > a.permalink,
+h4:hover > a.permalink,
+td:hover > a.permalink,
+td:hover > div > a.permalink,
+dt:hover > a.permalink {
+ visibility: visible;
+}
+
+pre.language-lua {
+ border-radius: 6px;
+}
+
+dd table {
+}
+
+input.search {
+ width: calc(100% - 2.5em);
+ opacity: 0.7;
+ margin: 0 1em 1em 1em;
+}
+
+input.search:focus {
+ opacity: 1.0;
+}
+
+div#template {
+ display: none;
+}
+
+div#results {
+ padding: 1em;
+ max-width: 650px;
+}
+
+div.result {
+ margin-bottom: 35px;
+ font-family: arial, sans-serif;
+}
+
+div.result div.title {
+ font-size: 20px;
+ line-height: 1.3;
+}
+
+div.result div.text {
+ line-height: 1.58;
+ margin-left: 2em;
+}
+
+div.summary {
+ color: #666666;
+ font-size: 90%;
+ margin-bottom: 1em;
+}
+
+div.result span {
+ font-size: 90%;
+}
+
+div.result.result-class span::after {
+ content: "🧱 ";
+}
+div.result.result-module span::after {
+ content: "📦 ";
+}
+div.result.result-field span::after {
+ content: "🏷️ ";
+}
+div.result.result-function span::after {
+ content: "🏃♂️ ";
+}
+div.result.result-section span::after {
+ content: "📓 ";
+}
+div.result b {
+ background-color: #faffb8;
+}
+
+table.user {
+ border-collapse: collapse;
+ margin: 1em;
+ font-family: sans-serif;
+ min-width: 400px;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
+ border-radius: 10px;
+ border-collapse: collapse;
+ overflow: hidden;
+}
+
+table.user thead tr {
+ background-color: #3a5e75;
+ color: #ffffff;
+ text-align: left;
+}
+
+table.user th,
+table.user td {
+ padding: 12px 15px;
+}
+
+table.user tbody tr {
+ border-bottom: 1px solid #dddddd;
+}
+
+table.user tbody tr:nth-of-type(even) {
+ background-color: #f3f3f3;
+}
+
+table.user tbody tr:last-of-type {
+ border-bottom: 3px solid #3a5e75;
+}
diff --git a/docs/out/module/Bullet_hole.html b/docs/out/module/Bullet_hole.html
new file mode 100644
index 0000000..50bbff5
--- /dev/null
+++ b/docs/out/module/Bullet_hole.html
@@ -0,0 +1,81 @@
+
+
+
+
+
+ Bullet_hole - Guns4d
+
+
+
+
+
+
allows you to play one or more sounds with more complex features, so sounds can be easily coded for guns without the need for functions.
+
WARNING: this function modifies the tables passed to it, use Guns4d.table.shallow_copy() or table.copy for inputted soundspecs
+
Example
+
{
+ to_player = "singeplayer",
+ min_distance = 100, --soundspec_to_play1 & soundspec_to_play2 will share this field as it is in the higher level table (as well as the above field)
+ soundspec_to_play1, --a sound parameter table
+ soundspec_to_play2
+}
+
simple specification for playing a sound in relation to an action, acts as a layer of minetest.play_sound.
+ATTENTION: read lua_api.md for more parameters! all parameters from there are valid unless otherwise stated here, these are auxillary.
+
Example
+
soundspec = {
+ sounds = { --weighted randoms
+ fire_fp = .5.
+ fire_fp_2 = .2.
+ fire_fp_3 = .3
+ },
+ pitch = {
+ min = .6,
+ max = 1
+ },
+ gain = 1, --format for pitch and gain is interchangable.
+ min_hear_distance = 20, --this is for distant gunshots, for example. Entirely optional. Cannot be used with to_player
+ exclude_player
+ to_player
+ --when present it automatically plays positionless audio, as this is for first person effects.
+}
+
objRef 4dguns changes to_player so it only plays positionless audio (as it is only intended for first person audio). If set to string "from_player" and player present
float the rate of dropoff for a sound. I figure this is a bit more intuitive then jacking the gain up super high for every sound... Set the default in config.
instance
- defines wether the object is an instance
-
-
base_class
- only present for instances: the class from which this instance originates
-
-
parent_class
- the class from which this class was inherited from
-
-
-
-
-
-
-
-
-
-
Methods
-
-
-
-
- instantiatable_class:inherit (def)
-
-
- creates a new base class. Calls all constructors in the chain with def.instance=true
-
-
-
Parameters:
-
-
def
- the table containing a new definition (where the class calling the method is the parent). The content of the definition will override the fields for it's children.
-
-
-
-
Returns:
-
-
- def a new base class
-
-
-
-
-
-
-
-
- instantiatable_class:construct ()
-
-
- construct
- every parent constructor is called in order of inheritance, this is used to make changes to the child table. In self you will find base_class defining what class it is from, and the bool instance indicating (shocking) wether it is an instance.
-
-
-
-
-
-
-
-
-
-
- instantiatable_class:new ()
-
-
- creates an instance of the base class. Calls all constructors in the chain with def.instance=true
-
-
-
-
Returns:
-
-
- def a new instance of the class.
-
-
-
-
-
-
-
-
-
-
-
-
-generated by LDoc 1.5.0
-Last updated 2024-06-18 17:20:58
-
- allows you to play one or more sounds with more complex features, so sounds can be easily coded for guns without the need for functions.
- WARNING: this function modifies the tables passed to it, use Guns4d.table.shallow_copy() for guns4d_soundspecs
-
-
-
{
- to_player = "singeplayer",
- min_distance = 100, --soundspec_to_play1 & soundspec_to_play2 share this parameter (as well as the to_player)
- soundspec_to_play1,
- soundspec_to_play2
- }
-
-
-
-
-
-
Returns:
-
-
- out a Guns4d sound handle (an integer)
-
-
-
-
-
-
-
-
- Guns4d.stop_sounds (handle_list)
-
-
- stops a list of sounds
-
-
-
Parameters:
-
-
handle_list
- a list of minetest sound handles to stop, this is the returned output of @{guns4d.play_sounds
-
-
-
-
-
-
-
-
-
-
Tables
-
-
-
-
- guns4d_soundspec
-
-
- defines a sound.
- This is passed to minetest.sound_play as a sound parameter table
- however has the following changed or guns4d specific parameters.
-
-
-
Fields:
-
-
min_hear_distance
- this is useful if you wish to play a sound which has a "far" sound, such as distant gunshots. incompatible with to_player
-
-
sounds
- a weighted_randoms table for randomly selecting sounds. The output will overwrite the sound field.
-
-
to_player
- 4dguns changes to_player so it only plays positionless audio (as it is only intended for first person audio). If set to string "from_player" and player present
-
-
player
- this is so toplayer being set to "fromplayer". It's to be set to the player which fired the weapon.
-
-
delay
- delay the playing of the sound
-
-
attenuation_rate
- float the rate of dropoff for a sound. I figure this is a bit more intuitive then jacking the gain up super high for every sound... Set the default in config.
-
-
split_audio_by_perspective
- true [GUN CLASS SPECIFIC] tells the gun wether to split into third and first person (positionless) audio and adjust gain.
-
-
third_person_gain_multiplier
- float [GUN CLASS SPECIFIC] replaces the constant/config value "thirdpersongainmultiplier/THIRDPERSONGAINMULTIPLIER".
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-generated by LDoc 1.5.0
-Last updated 2024-06-18 17:20:58
-
3dguns remastered. Currently a work in progress that is updating steadily (kind of).
-
-
-
-
-
-generated by LDoc 1.5.0
-Last updated 2024-06-18 17:20:58
-
-
-
-
diff --git a/docs/windows_quick_generate.bat b/docs/windows_quick_generate.bat
deleted file mode 100644
index 68b165d..0000000
--- a/docs/windows_quick_generate.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-# literally just so I dont have to open powershell every time.
-@echo off
-ldoc ./
\ No newline at end of file
diff --git a/item_entities.lua b/item_entities.lua
index 202a769..3c512f5 100644
--- a/item_entities.lua
+++ b/item_entities.lua
@@ -1,5 +1,3 @@
---- adds 3d items for guns and magazines
--- @script item_entities.lua
Guns4d.registered_items = {}
local old_spawn_item = core.spawn_item --didnt know if I had to use core instead of minetest or if they are the same reference, not chancing it though.
@@ -19,19 +17,8 @@ core.spawn_item = function(pos, item, ...)
return old_spawn_item(pos, item, ...)
end
---- table defining the new 3d entity for a dropped item
--- @field light_source int, equivelant to minetest itemdef version
--- @field size int, the size of the collision box
--- @field mesh string, the mesh to use for the item
--- @field textures table, a list of textures (see minetest entity documentation)
--- @field collisionbox_size, the size of collisionbox in tenths of meters.
--- @field selectionbox vector, xyz scale of the selectionbox
--- @field offset vector, xyz offset of the visual object from the collision and selectionbox. (so that magazines's origin can match their bone.)
--- @table guns4d_itemdef
-
local defaults = {
--light_source = 0,
- collisionbox_size = 2,
visual_size = 1,
realistic = Guns4d.config.realistic_items,
backface_culling = false,
@@ -39,10 +26,7 @@ local defaults = {
selectionbox = {-.2,-.2,-.2, .2,.2,.2},
collisionbox = (Guns4d.config.realistic_items and {-.2,-.05,-.2, .2,.15,.2}) or {-.2,-.2,-.2, .2,.2,.2}
}
---- replaces the item entity of the provided item with a 3d entity based on the definition
--- @param itemstring
--- @param def, a @{guns4d_itemdef}
--- @function Guns4d.register_item()
+
function Guns4d.register_item(itemstring, def)
assert(minetest.registered_items[itemstring], "item: `"..tostring(itemstring).."` not registered by minetest")
assert(type(def)=="table", "definition is not a table")
diff --git a/play_sound.lua b/play_sound.lua
index bd67909..db4ec91 100644
--- a/play_sound.lua
+++ b/play_sound.lua
@@ -1,59 +1,46 @@
--- implements tools for quickly playing audio.
--- @script play_sound
+-- @module play_sound
local sqrt = math.sqrt
---simple specification for playing a sound in relation to an action, acts as a layer of minetest.play_sound
---"gsp" guns4d-sound-spec
---first person for the gun holder, third person for everyone else. If first not present, third will be used.
---passes table directly to minetest.play_sound and adds a few additional parameters
---example:
---[[
- additional properties
- sounds = { --weighted randoms:
- fire_fp = .5.
- fire_fp_2 = .2.
- fire_fp_3 = .3
- },
- pitch = {
- min = .6,
- max = 1
- },
- gain = 1, --format for pitch and gain is interchangable.
- min_hear_distance = 20, --this is for distant gunshots, for example. Entirely optional. Cannot be used with to_player
- exclude_player
- to_player
- --when present it automatically plays positionless audio, as this is for first person effects.
-]]
---- defines a sound.
--- This is passed to `minetest.sound_play` as a [ sound parameter table](https://github.com/minetest/minetest/blob/master/doc/lua_api.md#sound-parameter-table)
--- however has the following changed or guns4d specific parameters.
--- @field min_hear_distance this is useful if you wish to play a sound which has a "far" sound, such as distant gunshots. incompatible `with to_player`
--- @field sounds a @{misc_helpers.weighted_randoms| weighted_randoms table} for randomly selecting sounds. The output will overwrite the `sound` field.
--- @field to_player 4dguns changes `to_player` so it only plays positionless audio (as it is only intended for first person audio). If set to string "from_player" and player present
--- @field player this is so to_player being set to "from_player". It's to be set to the player which fired the weapon.
--- @field delay delay the playing of the sound
--- @field attenuation_rate float the rate of dropoff for a sound. I figure this is a bit more intuitive then jacking the gain up super high for every sound... Set the default in config.
--- @field split_audio_by_perspective true [GUN CLASS SPECIFIC] tells the gun wether to split into third and first person (positionless) audio and adjust gain.
--- @field third_person_gain_multiplier float [GUN CLASS SPECIFIC] replaces the constant/config value "third_person_gain_multiplier/THIRD_PERSON_GAIN_MULTIPLIER".
+--- Guns4d soundspec
+--
+-- simple specification for playing a sound in relation to an action, acts as a layer of minetest.play_sound.
+-- ATTENTION: read lua_api.md for more parameters! all parameters from there are valid unless otherwise stated here, these are auxillary.
-- @table guns4d_soundspec
+-- @compact
+-- @field min_hear_distance `float` this is useful if you wish to play a sound which has a "far" sound, such as distant gunshots. incompatible `with to_player`
+-- @field sounds `table` a @{misc_helpers.weighted_randoms| weighted_randoms table} for randomly selecting sounds. The output will overwrite the `sound` field.
+-- @field to_player `objRef` 4dguns changes `to_player` so it only plays positionless audio (as it is only intended for first person audio). If set to string "from_player" and player present
+-- @field player `objRef` this is so to_player being set to "from_player". It's to be set to the player which fired the weapon.
+-- @field delay `float` delays the playing of the sound
+-- @field attenuation_rate `float` the rate of dropoff for a sound. I figure this is a bit more intuitive then jacking the gain up super high for every sound... Set the default in config.
+-- @field split_audio_by_perspective `bool` [GUN CLASS SPECIFIC] tells the gun wether to split into third and first person (positionless) audio and adjust gain.
+-- @field third_person_gain_multiplier `float` [GUN CLASS SPECIFIC] replaces the constant/config value "third_person_gain_multiplier/THIRD_PERSON_GAIN_MULTIPLIER".
+-- @example
+-- soundspec = {
+-- sounds = { --weighted randoms
+-- fire_fp = .5.
+-- fire_fp_2 = .2.
+-- fire_fp_3 = .3
+-- },
+-- pitch = {
+-- min = .6,
+-- max = 1
+-- },
+-- gain = 1, --format for pitch and gain is interchangable.
+-- min_hear_distance = 20, --this is for distant gunshots, for example. Entirely optional. Cannot be used with to_player
+-- exclude_player
+-- to_player
+-- --when present it automatically plays positionless audio, as this is for first person effects.
+-- }
+--
local function handle_min_max(tbl)
return tbl.min+(math.random()*(tbl.max-tbl.min))
end
---- allows you to play one or more sounds with more complex features, so sounds can be easily coded for guns without the need for functions.
--- WARNING: this function modifies the tables passed to it, use `Guns4d.table.shallow_copy()` for guns4d_soundspecs
--- @param sound_specs a @{guns4d_soundspec} or a list of @{guns4d_soundspec}s indexed my number. Also allows for shared fields. Example:
--- {
--- to_player = "singeplayer",
--- min_distance = 100, --soundspec_to_play1 & soundspec_to_play2 share this parameter (as well as the to_player)
--- soundspec_to_play1,
--- soundspec_to_play2
--- }
--- @return out a Guns4d sound handle (an integer)
--- @function Guns4d.play_sounds
local sound_handles = {}
local function play_sound(sound, soundspec, handle, i)
if soundspec.delay then
@@ -66,6 +53,19 @@ local function play_sound(sound, soundspec, handle, i)
sound_handles[handle][i] = minetest.sound_play(sound, soundspec)
end
end
+
+--- allows you to play one or more sounds with more complex features, so sounds can be easily coded for guns without the need for functions.
+--
+-- WARNING: this function modifies the tables passed to it, use `Guns4d.table.shallow_copy()` or `table.copy` for inputted soundspecs
+-- @example
+-- {
+-- to_player = "singeplayer",
+-- min_distance = 100, --soundspec_to_play1 & soundspec_to_play2 will share this field as it is in the higher level table (as well as the above field)
+-- soundspec_to_play1, --a sound parameter table
+-- soundspec_to_play2
+-- }
+-- @tparam table soundspecs_list a list a list of soundspecs optionally accompanied with fields to be used in all of them.
+-- @treturn integer guns4d sound handle, used by stop_sounds & get_sounds
function Guns4d.play_sounds(soundspecs_list)
--print(dump(soundspecs_list))
--support a list of sounds to play
@@ -133,18 +133,22 @@ function Guns4d.play_sounds(soundspecs_list)
end
return handle
end
--- @param handle a Guns4d sound handle
--- @function Guns4d.get_sounds gets a list of currently playing Minetest sound handles from the Guns4d sound handle. Modification not reccomended.
+
+--- gets a list of currently playing Minetest sound handles from the Guns4d sound handle. Modification of table highly discouraged.
+-- @tparam integer handle a sound handle as returned by play_sounds
+-- @treturn table a list of sound handles as returned by minetest.sound_play()
function Guns4d.get_sounds(handle)
return sound_handles[handle]
end
--- stops a list of sounds
--- @param handle_list a list of minetest sound handles to stop, this is the returned output of @{guns4d.play_sounds
--- @function Guns4d.stop_sounds
+-- @tparam integer|table handle a guns4d sound handle OR list of minetest sound handles to stop
+-- @treturn bool returns true if successful.
function Guns4d.stop_sounds(handle)
local handle_list = (type(handle) == "table" and handle) or sound_handles[handle]
if not handle_list then return false end
- sound_handles[handle] = false --indicate to not play any delayed noises.
+ if type(handle) == "number" then
+ sound_handles[handle] = false --indicate to not play any delayed noises.
+ end
for i, v in pairs(handle_list) do
minetest.sound_stop(v)
end