1130 lines
50 KiB
Lua
1130 lines
50 KiB
Lua
--- Gun class
|
|
--
|
|
-- ## 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 therefore 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|constants} which define gun attributes and should not be changed in an instance of the gun
|
|
-- @field offsets runtime storage of @{lvl1_fields.offsets|offsets} generated by recoil sway wag or any other element.
|
|
-- @compact
|
|
local gun_default = {
|
|
--- `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 = "",
|
|
--- 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,
|
|
--- `vec3` translation of the gun relative to the "gun" bone or the player axial rotation.
|
|
gun_translation = vector.new(),
|
|
|
|
--- properties
|
|
--
|
|
-- the table containing every attribute of the gun.
|
|
-- @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 = {
|
|
|
|
--- `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}
|
|
},
|
|
--- 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,
|
|
},
|
|
--- 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 = .1,
|
|
--- the time it takes to go into full aim
|
|
aim_time = 1,
|
|
},
|
|
--- `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.
|
|
},
|
|
--- `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"
|
|
},
|
|
--- 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 = 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},
|
|
},
|
|
--- `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
|
|
},
|
|
},
|
|
--- 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
|
|
}
|
|
},
|
|
--- 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,
|
|
auto = Guns4d.default_controls.auto,
|
|
reload = Guns4d.default_controls.reload,
|
|
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,
|
|
auto = Guns4d.default_touch_controls.auto,
|
|
reload = Guns4d.default_touch_controls.reload,
|
|
on_secondary_use = Guns4d.default_touch_controls.on_secondary_use,
|
|
firemode = Guns4d.default_touch_controls.firemode
|
|
},
|
|
--- 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
|
|
},
|
|
--- 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,
|
|
},
|
|
--- 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 = {},
|
|
--- `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 = {
|
|
--- toggles backface culling
|
|
backface_culling = true,
|
|
--- 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.
|
|
fire = {
|
|
{
|
|
__overfill=true,
|
|
sound = "ar_firing",
|
|
max_hear_distance = 40, --far min_hear_distance is also this.
|
|
pitch = {
|
|
min = .95,
|
|
max = 1.05
|
|
},
|
|
gain = {
|
|
min = .9,
|
|
max = 1
|
|
}
|
|
},
|
|
{
|
|
__overfill=true,
|
|
sound = "ar_firing_far",
|
|
min_hear_distance = 40,
|
|
max_hear_distance = 600,
|
|
pitch = {
|
|
min = .95,
|
|
max = 1.05
|
|
},
|
|
gain = {
|
|
min = .9,
|
|
max = 1
|
|
}
|
|
}
|
|
},
|
|
},
|
|
ammo_handler = Ammo_handler,
|
|
sprite_scope = nil,
|
|
crosshair = nil,
|
|
initial_vertical_rotation = -60,
|
|
},
|
|
--- offsets
|
|
--
|
|
-- a list of tables each containing offset vectors These are required for automatic initialization of offsets.
|
|
-- @example
|
|
-- recoil = {
|
|
-- gun_axial = {x=0, y=0}, --rotation of the gun around it's origin
|
|
-- player_axial = {x=0, y=0}, --rotation of the gun around the bone it's attached to
|
|
-- --translations of gun
|
|
-- player_trans = {x=0, y=0, z=0}, --translation of the bone the gun is attached to
|
|
-- eye_trans = {x=0, y=0, z=0}, --trnaslation of the player's look
|
|
-- gun_tran = {x=0, y=0, z=0}s --translation of the gun relative to the bone it's attachted to.
|
|
-- }
|
|
-- @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 = {
|
|
gun_axial = Vec.new(),
|
|
player_axial = Vec.new(),
|
|
look_trans = Vec.new(),
|
|
player_trans = Vec.new(),
|
|
gun_trans = Vec.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_offsets = {
|
|
gun_axial = vector.new(), rotation of the gun entity (around entity's own axis)
|
|
player_axial = vector.new(), rotation around the eye (the player's axis)
|
|
gun_trans = vector.new(), translation of the gun relative to attached bone's rotation
|
|
player_trans = vector.new(), translation of the gun relative to the player's eye
|
|
look_trans = vector.new() translation/offset of the player's eye
|
|
}, ]]
|
|
--player_rotation = Vec.new(),
|
|
velocities = {
|
|
recoil = {
|
|
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(),
|
|
},
|
|
},
|
|
--- 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 = {
|
|
---
|
|
AIM_OUT_AIM_IN_SPEED_RATIO = 2.5,
|
|
--- 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,
|
|
--- `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,
|
|
--- 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",
|
|
},
|
|
}
|
|
|
|
|
|
--I dont remember why I made this, used it though lmao
|
|
function gun_default.multiplier_coefficient(multiplier, ratio)
|
|
return 1+((multiplier*ratio)-ratio)
|
|
end
|
|
function gun_default:draw()
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
local props = self.properties
|
|
if props.visuals.animations[props.charging.draw_animation] then
|
|
self:set_animation(props.visuals.animations[props.charging.draw_animation], props.charging.draw_time)
|
|
end
|
|
if props.sounds[props.charging.draw_sound] then
|
|
local sounds = Guns4d.table.deep_copy(props.sounds[props.charging.draw_sound])
|
|
self:play_sounds(sounds)
|
|
end
|
|
self.ammo_handler:chamber_round()
|
|
self.rechamber_time = props.charging.draw_time
|
|
end
|
|
--update gun, the main function.
|
|
function gun_default:update(dt)
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
if not self:has_entity() then self:add_entity(); self:clear_animation() end
|
|
local handler = self.handler
|
|
|
|
--player look rotation. I'm going to keep it real, I don't remember what this math does. Player handler just stores the player's rotation from MT in degrees, which is for some reason inverted
|
|
|
|
--timers
|
|
if self.rechamber_time > 0 then
|
|
self.rechamber_time = self.rechamber_time - dt
|
|
else
|
|
self.rechamber_time = 0
|
|
end
|
|
self.time_since_creation = self.time_since_creation + dt
|
|
self.time_since_last_fire = self.time_since_last_fire + dt
|
|
|
|
if self.burst_queue > 0 then self:update_burstfire() end
|
|
--update some vectors
|
|
self:update_look_offsets(dt)
|
|
if self.consts.HAS_SWAY then self:update_sway(dt) end
|
|
if self.consts.HAS_RECOIL then self:update_recoil(dt) end
|
|
if self.consts.HAS_BREATHING then self:update_breathing(dt) end
|
|
if self.consts.HAS_WAG then self:update_wag(dt) end
|
|
|
|
self:update_animation(dt)
|
|
self.dir = self:get_dir()
|
|
self.local_dir = self:get_dir(true)
|
|
self.paxial_dir = self:get_player_axial_dir()
|
|
self.local_paxial_dir = self:get_player_axial_dir(true)
|
|
self.pos = self:get_pos()+self.handler:get_pos()
|
|
self:update_entity()
|
|
|
|
if self.properties.sprite_scope then
|
|
self.sprite_scope:update()
|
|
end
|
|
if self.properties.crosshair then
|
|
self.crosshair:update()
|
|
end
|
|
local total_offset = self.total_offsets
|
|
--axis rotations
|
|
total_offset.player_axial.x = 0; total_offset.player_axial.y = 0
|
|
total_offset.gun_axial.x = 0; total_offset.gun_axial.y = 0
|
|
--translations
|
|
total_offset.player_trans.x = 0; total_offset.player_trans.y = 0; total_offset.player_trans.z = 0
|
|
total_offset.gun_trans.x = 0; total_offset.gun_trans.y = 0; total_offset.gun_trans.z = 0
|
|
total_offset.look_trans.x = 0; total_offset.look_trans.y = 0; total_offset.look_trans.z = 0
|
|
--this doesnt work.
|
|
for type, _ in pairs(total_offset) do
|
|
for i, offset in pairs(self.offsets) do
|
|
if offset[type] and (self.consts.HAS_GUN_AXIAL_OFFSETS or type~="gun_axial") then
|
|
total_offset[type] = total_offset[type]+offset[type]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function gun_default:update_burstfire()
|
|
if self.rechamber_time <= 0 then
|
|
local success = self:attempt_fire()
|
|
if not success then
|
|
self.burst_queue = 0
|
|
else
|
|
self.burst_queue = self.burst_queue - 1
|
|
end
|
|
end
|
|
end
|
|
function gun_default:cycle_firemodes()
|
|
--cannot get length using length operator because it's a proxy table
|
|
local length = 0
|
|
for i, v in ipairs(self.properties.firemodes) do
|
|
length = length+1
|
|
end
|
|
self.current_firemode = ((self.current_firemode)%(length))+1
|
|
|
|
self.meta:set_int("guns4d_firemode", self.current_firemode)
|
|
self:update_image_and_text_meta()
|
|
self.player:set_wielded_item(self.itemstack)
|
|
end
|
|
--remember to set_wielded_item to self.itemstack! otherwise these changes will not apply!
|
|
function gun_default:update_image_and_text_meta(meta)
|
|
meta = meta or self.meta
|
|
local ammo = self.ammo_handler.ammo
|
|
--set the counter
|
|
if ammo.total_bullets == 0 then
|
|
meta:set_string("count_meta", Guns4d.config.empty_symbol)
|
|
else
|
|
if Guns4d.config.show_gun_inv_ammo_count then
|
|
meta:set_string("count_meta", tostring(ammo.total_bullets))
|
|
else
|
|
meta:set_string("count_meta", "F")
|
|
end
|
|
end
|
|
--pick the image
|
|
local image = self.properties.inventory_image
|
|
if (ammo.total_bullets > 0) and not ammo.magazine_psuedo_empty then
|
|
image = self.properties.inventory_image
|
|
elseif self.properties.inventory_image_magless and ( (ammo.loaded_mag == "empty") or (ammo.loaded_mag == "") or ammo.magazine_psuedo_empty) then
|
|
image = self.properties.inventory_image_magless
|
|
elseif self.properties.inventory_image_empty then
|
|
image = self.properties.inventory_image_empty
|
|
end
|
|
--add the firemode overlay to the image
|
|
local firemodes = 0
|
|
for i, v in pairs(self.properties.firemodes) do
|
|
firemodes = firemodes+1
|
|
end
|
|
if firemodes > 1 and self.properties.firemode_inventory_overlays[self.properties.firemodes[self.current_firemode]] then
|
|
image = image.."^"..self.properties.firemode_inventory_overlays[self.properties.firemodes[self.current_firemode]]
|
|
end
|
|
if self.handler.infinite_ammo then
|
|
image = image.."^"..self.properties.infinite_inventory_overlay
|
|
end
|
|
meta:set_string("inventory_image", image)
|
|
end
|
|
function gun_default:attempt_fire()
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
if self.rechamber_time <= 0 and not self.ammo_handler.ammo.magazine_psuedo_empty then
|
|
local spent_bullet = self.ammo_handler:spend_round()
|
|
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*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 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(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/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
|
|
if math.random() > b then int=-1 end
|
|
return int
|
|
end
|
|
function gun_default:recoil()
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
local rprops = self.properties.recoil
|
|
for axis, recoil in pairs(self.velocities.recoil) do
|
|
for _, i in pairs({"x","y"}) do
|
|
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.control_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
|
|
end
|
|
function gun_default:update_look_offsets(dt)
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
local handler = self.handler
|
|
local look_rotation = handler.look_rotation --remember that this is in counterclock-wise rotation. For 4dguns we use clockwise so it makes a bit more sense for recoil. So it needs to be inverted.
|
|
local player_rot = self.player_rotation
|
|
player_rot.y = -handler.look_rotation.y
|
|
local rot_factor = Guns4d.config.vertical_rotation_factor*dt
|
|
rot_factor = rot_factor
|
|
local next_vert_aim = ((player_rot.x-look_rotation.x)/(1+rot_factor))+look_rotation.x --difference divided by a value and then added back to the original
|
|
if math.abs(look_rotation.x-next_vert_aim) > .005 then
|
|
player_rot.x = next_vert_aim
|
|
else
|
|
player_rot.x = look_rotation.x
|
|
end
|
|
|
|
local props = self.properties
|
|
local hip = props.hip
|
|
local ads = props.ads
|
|
if not handler.control_handler.ads then
|
|
--hipfire rotation offsets
|
|
local pitch = self.total_offsets.player_axial.x+player_rot.x
|
|
local gun_axial = self.offsets.look.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-hip.axis_rotation_ratio))
|
|
self.offsets.look.player_axial.x = -pitch*(1-hip.axis_rotation_ratio)
|
|
self.offsets.look.look_trans.x = 0
|
|
else
|
|
self.offsets.look.gun_axial.x = 0
|
|
self.offsets.look.player_axial.x = 0
|
|
end
|
|
local location = Guns4d.math.clamp(Guns4d.math.smooth_ratio(self.control_handler.ads_location)*2, 0, 1)
|
|
self.offsets.look.look_trans.x = ads.horizontal_offset*location
|
|
local fwd_offset = 0
|
|
if look_rotation.x < 0 then --minetest's pitch is inverted, checking here if it's above horizon.
|
|
fwd_offset = math.abs(math.sin(look_rotation.x*math.pi/180))*props.ads.offset.z*location
|
|
end
|
|
self.offsets.look.player_trans.z = fwd_offset
|
|
self.offsets.look.look_trans.z = fwd_offset
|
|
end
|
|
--============================================== positional info =====================================
|
|
--all of this dir shit needs to be optimized HARD
|
|
function gun_default:get_gun_axial_dir()
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
local rotation = self.total_offsets
|
|
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=rotation.gun_axial.x*math.pi/180, z=0}))
|
|
dir = Vec.rotate(dir, {y=rotation.gun_axial.y*math.pi/180, x=0, z=0})
|
|
return dir
|
|
end
|
|
function gun_default:get_player_axial_dir(rltv)
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
local handler = self.handler
|
|
local rotation = self.total_offsets
|
|
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.player_axial.x)*math.pi/180), z=0}))
|
|
dir = Vec.rotate(dir, {y=((rotation.player_axial.y)*math.pi/180), x=0, z=0})
|
|
if not rltv then
|
|
if (self.properties.sprite_scope and handler.control_handler.ads) or (self.properties.crosshair and not handler.control_handler.ads) then
|
|
--we need the head rotation in either of these cases, as that's what they're showing.
|
|
dir = Vec.rotate(dir, {x=handler.look_rotation.x*math.pi/180,y=-handler.look_rotation.y*math.pi/180,z=0})
|
|
else
|
|
dir = Vec.rotate(dir, {x=self.player_rotation.x*math.pi/180,y=self.player_rotation.y*math.pi/180,z=0})
|
|
end
|
|
end
|
|
return dir
|
|
end
|
|
--this needs to be optimized because it may be called frequently...
|
|
function gun_default:get_dir(rltv, offset_x, offset_y)
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
local rotation = self.total_offsets
|
|
local handler = self.handler
|
|
--rotate x and then y.
|
|
--used symbolab.com to precalculate the rotation matrices to save on performance since spread for pellets has to run this.
|
|
local p = -(rotation.gun_axial.x+rotation.player_axial.x+(offset_x or 0))*math.pi/180
|
|
local y = -(rotation.gun_axial.y+rotation.player_axial.y+(offset_y or 0))*math.pi/180
|
|
local Cy = math.cos(y)
|
|
local Sy = math.sin(y)
|
|
local Cp = math.cos(p)
|
|
local Sp = math.sin(p)
|
|
local dir = {
|
|
x=Sy*Cy,
|
|
y=-Sp,
|
|
z=Cy*Cp
|
|
}
|
|
if not rltv then
|
|
p = -self.player_rotation.x*math.pi/180
|
|
y = -self.player_rotation.y*math.pi/180
|
|
Cy = math.cos(y)
|
|
Sy = math.sin(y)
|
|
Cp = math.cos(p)
|
|
Sp = math.sin(p)
|
|
dir = vector.new(
|
|
(Cy*dir.x)+(Sy*Sp*dir.y)+(Sy*Cp*dir.z),
|
|
(dir.y*Cp)-(dir.z*Sp),
|
|
(-dir.x*Sy)+(dir.y*Sp*Cy)+(dir.z*Cy*Cp)
|
|
)
|
|
else
|
|
dir = vector.new(dir)
|
|
end
|
|
return dir
|
|
end
|
|
--Should probably optimize this at some point.
|
|
local zero = vector.zero()
|
|
function gun_default:get_pos(offset_pos, relative, ads, ignore_translations)
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
local player = self.player
|
|
local handler = self.handler
|
|
local bone_location = handler.player_model_handler.gun_bone_location
|
|
local gun_translation = self.gun_translation
|
|
if offset_pos then
|
|
gun_translation = gun_translation+offset_pos
|
|
end
|
|
if gun_translation==self.gun_translation then gun_translation = vector.new(gun_translation) end
|
|
--dir needs to be rotated twice seperately to avoid weirdness
|
|
local pos
|
|
if not relative then
|
|
pos = Vec.rotate(bone_location, {x=0, y=-handler.look_rotation.y*math.pi/180, z=0})
|
|
pos = pos+Vec.rotate(gun_translation, Vec.dir_to_rotation(self.paxial_dir))
|
|
else
|
|
pos = Vec.rotate(gun_translation, Vec.dir_to_rotation(self.local_paxial_dir)+{x=self.player_rotation.x*math.pi/180,y=0,z=0})+bone_location
|
|
end
|
|
--[[local hud_pos
|
|
if relative then
|
|
hud_pos = vector.rotate(pos, {x=0,y=player:get_look_horizontal(),z=0})+handler:get_pos()
|
|
else
|
|
hud_pos = pos+handler:get_pos()
|
|
end]]
|
|
if minetest.get_player_by_name("fatal2") then
|
|
--[[local hud = minetest.get_player_by_name("fatal2"):hud_add({
|
|
hud_elem_type = "image_waypoint",
|
|
text = "muzzle_flash2.png",
|
|
world_pos = hud_pos,
|
|
scale = {x=10, y=10},
|
|
alignment = {x=0,y=0},
|
|
offset = {x=0,y=0},
|
|
})
|
|
minetest.after(0, function(hud)
|
|
minetest.get_player_by_name("fatal2"):hud_remove(hud)
|
|
end, hud)]]
|
|
end
|
|
|
|
--world pos, position of bone, offset of gun from bone (with added_pos)
|
|
return pos
|
|
end
|
|
|
|
|
|
--=============================================== ENTITY ======================================================
|
|
|
|
|
|
function gun_default:add_entity()
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
self.entity = minetest.add_entity(self.player:get_pos(), self.name.."_visual")
|
|
local obj = self.entity:get_luaentity()
|
|
--obj.parent_player = self.player
|
|
Guns4d.gun_by_ObjRef[self.entity] = self
|
|
--obj:on_step()
|
|
--self:update_entity()
|
|
end
|
|
function gun_default:update_entity()
|
|
local obj = self.entity
|
|
local player = self.player
|
|
local axial_rot = self.total_offsets.gun_axial
|
|
local handler = self.handler
|
|
local props = self.properties
|
|
--attach to the correct bone, and rotate
|
|
local visibility = true
|
|
if self.sprite_scope and self.sprite_scope.hide_gun and (not (self.control_handler.ads_location == 0)) then
|
|
visibility = false
|
|
end
|
|
--Irrlicht uses counterclockwise but we use clockwise.
|
|
local pos = self.gun_translation
|
|
local ads = props.ads.offset
|
|
local hip = props.hip.offset
|
|
local offset = self.total_offsets.gun_trans
|
|
local ip = Guns4d.math.smooth_ratio(handler.control_handler.ads_location)
|
|
local ip_inv = 1-ip
|
|
pos.x = (ads.x*ip)+(hip.x*ip_inv)+offset.x
|
|
pos.y = (ads.y*ip)+(hip.y*ip_inv)+offset.y
|
|
pos.z = (ads.z*ip)+(hip.z*ip_inv)+offset.z
|
|
self.gun_translation = pos
|
|
obj:set_attach(player, handler.player_model_handler.bone_aliases.gun, {x=pos.x*10, y=pos.y*10, z=pos.z*10}, -axial_rot, visibility)
|
|
end
|
|
function gun_default:has_entity()
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
if not self.entity then return false end
|
|
if not self.entity:get_pos() then return false end
|
|
return true
|
|
end
|
|
|
|
|
|
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()
|
|
wag.velocity = velocity
|
|
end
|
|
old_tick = old_tick or wag.tick
|
|
if velocity then
|
|
if handler.walking then
|
|
wag.tick = wag.tick + (dt*Vec.length(velocity))
|
|
else
|
|
wag.tick = wag.tick + (dt*4)
|
|
end
|
|
end
|
|
local walking_offset = self.offsets.walking
|
|
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
|
|
for _, i in ipairs({"x","y"}) do
|
|
for _, axis in ipairs({"player_axial", "gun_axial"}) do
|
|
if velocity then
|
|
local multiplier = 1
|
|
if i == "x" then
|
|
multiplier = 2
|
|
end
|
|
--if the result is negative we know that it's flipped, and thus can be ended.
|
|
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.wag.offset[axis][i]
|
|
else
|
|
local old_value = walking_offset[axis][i]
|
|
if math.abs(walking_offset[axis][i]) > .005 then
|
|
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
|
|
end
|
|
if math.abs(walking_offset[axis][i]) > math.abs(old_value) then
|
|
walking_offset[axis][i] = 0
|
|
end
|
|
end
|
|
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
|
|
local recoil = self.offsets.recoil[axis][i]
|
|
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
|
|
--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
|
|
self.velocities.recoil[axis][i] = recoil_vel
|
|
|
|
--ax^2+bx+c
|
|
--recoil_velocity_correction_rate
|
|
--recoil_correction_rate
|
|
local old_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 abs < 0 then
|
|
abs = 0
|
|
end
|
|
end
|
|
if sign~=sign then
|
|
sign = 1
|
|
end
|
|
self.offsets.recoil[axis][i] = abs*sign
|
|
end
|
|
end
|
|
end
|
|
function gun_default:update_animation(dt)
|
|
local ent = self.entity
|
|
local data = self.animation_data
|
|
data.runtime = data.runtime + dt
|
|
data.current_frame = Guns4d.math.clamp(data.current_frame+(dt*data.fps), data.frames.x, data.frames.y)
|
|
if data.loop and (data.current_frame > data.frames.y) then
|
|
data.current_frame = data.frames.x
|
|
end
|
|
--track rotations and applies to aim.
|
|
if self.consts.ANIMATIONS_OFFSET_AIM then self:update_animation_rotation() end
|
|
end
|
|
--IMPORTANT!!! this does not directly modify the animation_data table anymore, it's all hooked through ObjRef:set_animation() (init.lua) so if animation is set elsewhere it doesnt break.
|
|
--this may be deprecated in the future- as it is no longer really needed now that I hook ObjRef functions.
|
|
function gun_default:set_animation(frames, length, fps, loop)
|
|
loop = loop or false --why the fuck default is true? I DONT FUCKIN KNOW (this undoes this)
|
|
assert(type(frames)=="table" and frames.x and frames.y, "frames invalid or nil in set_animation()!")
|
|
assert(not (length and fps), "cannot play animation with both specified length and specified fps. Only one parameter can be used.")
|
|
local num_frames = math.abs(frames.x-frames.y)
|
|
if length then
|
|
fps = num_frames/length
|
|
elseif not fps then
|
|
fps = self.consts.DEFAULT_FPS
|
|
end
|
|
self.entity:set_animation(frames, fps, 0, loop) --see init.lua for modified ObjRef stuff.
|
|
end
|
|
function gun_default:clear_animation()
|
|
local loaded = false
|
|
if self.properties.ammo.magazine_only then
|
|
if self.ammo_handler.ammo.loaded_mag ~= "empty" then
|
|
loaded = true
|
|
end
|
|
elseif self.ammo_handler.ammo.total_bullets > 0 then
|
|
loaded = true
|
|
end
|
|
if loaded then
|
|
self.entity:set_animation({x=self.properties.visuals.animations.loaded.x, y=self.properties.visuals.animations.loaded.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
|
|
else
|
|
self.entity:set_animation({x=self.properties.visuals.animations.empty.x, y=self.properties.visuals.animations.empty.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
|
|
end
|
|
end
|
|
local function adjust_gain(tbl, v)
|
|
v = tbl.third_person_gain_multiplier or v
|
|
for i = 1, #tbl do
|
|
adjust_gain(tbl[i], v)
|
|
end
|
|
if tbl.gain and (tbl.split_audio_by_perspective~=false) then
|
|
if type(tbl.gain) == "number" then
|
|
tbl.gain = tbl.gain*v
|
|
else
|
|
tbl.gain.min = tbl.gain.min*v
|
|
tbl.gain.max = tbl.gain.max*v
|
|
end
|
|
end
|
|
end
|
|
function gun_default:play_sounds(sound)
|
|
local thpson_sound = Guns4d.table.deep_copy(sound)
|
|
local fsprsn_sound = Guns4d.table.deep_copy(sound)
|
|
|
|
thpson_sound.pos = self.pos
|
|
thpson_sound.player = self.player
|
|
thpson_sound.exclude_player = self.player
|
|
adjust_gain(thpson_sound, self.consts.THIRD_PERSON_GAIN_MULTIPLIER)
|
|
|
|
fsprsn_sound.player = self.player
|
|
fsprsn_sound.to_player = "from_player"
|
|
|
|
return Guns4d.play_sounds(thpson_sound), Guns4d.play_sounds(fsprsn_sound)
|
|
end
|
|
function gun_default:update_breathing(dt)
|
|
assert(self.instance)
|
|
local breathing_info = {pause=1.4, rate=4.2}
|
|
--we want X to be between 0 and 4.2. Since math.pi is a positive crest, we want X to be above it before it reaches our-
|
|
--"length" (aka rate-pause), thus it will pi/length or pi/(rate-pause) will represent out slope of our control.
|
|
local x = (self.time_since_creation%breathing_info.rate)*math.pi/(breathing_info.rate-breathing_info.pause)
|
|
local scale = self.properties.breathing_scale
|
|
--now if it's above math.pi we know it's in the pause half of the cycle. For smoothness, we cut the sine off early and decay the value non-linearly.
|
|
--not sure why 8/9 is a constant here... I assume it's if it's 8/9 of the way through the cycle. Not going to worry about it.
|
|
if x > math.pi*(8/9) then
|
|
self.offsets.breathing.player_axial.x=self.offsets.breathing.player_axial.x-(self.offsets.breathing.player_axial.x*2*dt)
|
|
else
|
|
self.offsets.breathing.player_axial.x = scale*(math.sin(x))
|
|
end
|
|
end
|
|
|
|
function gun_default:update_sway(dt)
|
|
assert(self.instance, "attempt to call object method from a base class")
|
|
local sprops = self.properties.sway
|
|
for axis, sway in pairs(self.offsets.sway) do
|
|
local sway_vel = self.velocities.sway[axis]
|
|
local ran
|
|
ran = Vec.apply(Vec.new(), function(i,v)
|
|
if i ~= "x" then
|
|
return (math.random()-.5)*2
|
|
end
|
|
end)
|
|
ran.z = 0
|
|
local vel_mul = self.multiplier_coefficient(sprops.hipfire_velocity_multiplier[axis], 1-self.control_handler.ads_location)
|
|
sway_vel = Vec.normalize(sway_vel+(ran*dt))*sprops.angular_velocity[axis]*vel_mul
|
|
sway=sway+(sway_vel*dt)
|
|
local len_mul = self.multiplier_coefficient(sprops.hipfire_angle_multiplier[axis], 1-self.control_handler.ads_location)
|
|
if Vec.length(sway) > sprops.max_angle[axis]*len_mul then
|
|
sway=Vec.normalize(sway)*sprops.max_angle[axis]*len_mul
|
|
sway_vel = Vec.new()
|
|
end
|
|
self.offsets.sway[axis] = sway
|
|
self.velocities.sway[axis] = sway_vel
|
|
end
|
|
end
|
|
|
|
function gun_default:update_animation_rotation()
|
|
local current_frame = self.animation_data.current_frame+self.consts.KEYFRAME_SAMPLE_PRECISION
|
|
local frame1 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)
|
|
local frame2 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)+1
|
|
current_frame = current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION
|
|
local out
|
|
if self.b3d_model.global_frames.rotation then
|
|
if self.b3d_model.global_frames.rotation[frame1] then
|
|
if (not self.b3d_model.global_frames.rotation[frame2]) or (current_frame==frame1) then
|
|
out = vector.new(self.b3d_model.global_frames.rotation[frame1]:to_euler_angles_unpack())*180/math.pi
|
|
--print("rawsent")
|
|
else --to stop nan
|
|
local ip_ratio = (current_frame-frame1)/(frame2-frame1)
|
|
local vec1 = self.b3d_model.global_frames.rotation[frame1]
|
|
local vec2 = self.b3d_model.global_frames.rotation[frame2]
|
|
out = vector.new(vec1:slerp(vec2, ip_ratio):to_euler_angles_unpack())*180/math.pi
|
|
end
|
|
else
|
|
out = vector.copy(self.b3d_model.global_frames.rotation[1])
|
|
end
|
|
--print(frame1, frame2, current_frame, dump(out))
|
|
else
|
|
out = vector.new()
|
|
end
|
|
self.animation_rotation = out
|
|
end
|
|
|
|
--relative to the gun's entity. Returns left, right vectors.
|
|
local out = {arm_left=vector.new(), arm_right=vector.new()}
|
|
function gun_default:get_arm_aim_pos()
|
|
local current_frame = self.animation_data.current_frame+1
|
|
local frame1 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)
|
|
local frame2 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)+1
|
|
current_frame = current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION
|
|
|
|
for i, v in pairs(out) do
|
|
if self.b3d_model.global_frames[i] then
|
|
if self.b3d_model.global_frames[i][frame1] then
|
|
if (not self.b3d_model.global_frames[i][frame2]) or (current_frame==frame1) then
|
|
out[i] = vector.copy(self.b3d_model.global_frames[i][frame1])
|
|
else --to stop nan
|
|
local ip_ratio = (current_frame-frame1)/(frame2-frame1)
|
|
local vec1 = self.b3d_model.global_frames[i][frame1]
|
|
local vec2 = self.b3d_model.global_frames[i][frame2]
|
|
print(current_frame, frame1, frame2, ip_ratio)
|
|
out[i] = vec1+((vec1-vec2)*ip_ratio)
|
|
end
|
|
else
|
|
out[i]=vector.copy(self.b3d_model.global_frames[i][1])
|
|
end
|
|
else
|
|
out[i] = vector.new()
|
|
end
|
|
end
|
|
return out.arm_left, out.arm_right
|
|
--return vector.copy(self.b3d_model.global_frames.arm_left[1]), vector.copy(self.b3d_model.global_frames.arm_right[1])
|
|
end
|
|
|
|
function gun_default:prepare_deletion()
|
|
self.released = true
|
|
assert(self.instance, "attempt to call object method on a class")
|
|
if self:has_entity() then self.entity:remove() end
|
|
if self.sprite_scope then self.sprite_scope:prepare_deletion() end
|
|
if self.crosshair then self.crosshair:prepare_deletion() end
|
|
end
|
|
|
|
Guns4d.gun = gun_default
|
|
dofile(minetest.get_modpath("guns4d").."/classes/gun_construct.lua")
|
|
|
|
gun_default.construct = function(def)
|
|
if def.instance then
|
|
gun_default.construct_instance(def)
|
|
elseif def.name ~= "__guns4d:default__" then
|
|
--print(dump(def))
|
|
gun_default.construct_base_class(def)
|
|
end
|
|
end
|
|
Guns4d.gun = mtul.class.new_class:inherit(gun_default) |