completely reworked sound
This commit is contained in:
parent
8212981d2d
commit
c2da4ad0cb
@ -64,7 +64,7 @@ function Guns4d.ammo.register_magazine(def)
|
||||
def.accepted_bullets_set = {} --this table is a "lookup" table, I didn't go to college so I have no idea
|
||||
for i, v in pairs(def.accepted_bullets) do
|
||||
--TODO: make an actual error/minetest.log
|
||||
if not Guns4d.ammo.registered_bullets[v] then print("guns4D: WARNING! bullet "..v.." not registered! is this a mistake?") end
|
||||
if not Guns4d.ammo.registered_bullets[v] then print("guns4D: WARNING! bullet "..v.." not registered! is this a mistake?") end --TODO replace with minetest.log
|
||||
def.accepted_bullets_set[v] = true
|
||||
end
|
||||
Guns4d.ammo.registered_magazines[def.itemstring] = def
|
||||
|
@ -182,14 +182,36 @@ function Ammo_handler:load_magazine()
|
||||
return
|
||||
end
|
||||
end
|
||||
function Ammo_handler:inventory_has_ammo()
|
||||
function Ammo_handler:load_single_cartridge()
|
||||
local inv = self.inventory
|
||||
local gun = self.gun
|
||||
local ammo = self.ammo
|
||||
local bullet
|
||||
if self.ammo.total_bullets >= gun.properties.ammo.capacity then return false end
|
||||
for i, v in pairs(inv:get_list("main")) do
|
||||
if gun.accepted_bullets[v:get_name()] then
|
||||
self:update_meta()
|
||||
bullet = v:get_name()
|
||||
v:take_item(1)
|
||||
inv:set_stack("main", i, v)
|
||||
end
|
||||
end
|
||||
if bullet then
|
||||
ammo.loaded_bullets[bullet] = (ammo.loaded_bullets[bullet] or 0)+1
|
||||
ammo.total_bullets = ammo.total_bullets+1
|
||||
self:update_meta()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
function Ammo_handler:inventory_has_ammo(only_cartridges)
|
||||
local inv = self.inventory
|
||||
local gun = self.gun
|
||||
for i, v in pairs(inv:get_list("main")) do
|
||||
if gun.accepted_magazines[v:get_name()] and (tally_ammo_from_meta(v:get_meta())>0) then
|
||||
if (not only_cartridges) and gun.accepted_magazines[v:get_name()] and (tally_ammo_from_meta(v:get_meta())>0) then
|
||||
return true
|
||||
end
|
||||
if (not gun.properties.ammo.magazine_only) and gun.accepted_bullets[v:get_name()] then
|
||||
if ((only_cartridges or not gun.properties.ammo.magazine_only) and gun.accepted_bullets[v:get_name()]) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
@ -1,62 +1,153 @@
|
||||
local player_positions = {}
|
||||
minetest.register_globalstep(function(dt)
|
||||
|
||||
end)
|
||||
|
||||
Bullet_hole = Instantiatable_class:inherit({
|
||||
unrendered_exptime = 20,
|
||||
unrendered_texture = 'bullet_hole.png',
|
||||
expiration_time = 60,
|
||||
heat_effect = false,
|
||||
render_distance = 50,
|
||||
deletion_distance = 80,
|
||||
timer = 0,
|
||||
construct = function(def)
|
||||
assert(def.pos)
|
||||
for i, v in pairs(player_positions) do
|
||||
player_positions[i] = nil
|
||||
end
|
||||
for i, player_handler in pairs(Guns4d.players) do
|
||||
table.insert(player_positions, player_handler.player:get_pos())
|
||||
end
|
||||
local count = 0
|
||||
for i = #Guns4d.bullet_hole.instances+1, 1, -1 do --start at the last so the bullet_holes added sooner are expired.
|
||||
local obj = Guns4d.bullet_hole.instances[i]
|
||||
if obj then --obj can be a false value.
|
||||
if (count > Guns4d.config.maximum_bullet_holes) and (obj.exp_time > 2.5) then
|
||||
obj.exp_time = 2.5
|
||||
end
|
||||
count = count + 1
|
||||
local closest_dist
|
||||
for _, pos in pairs(player_positions) do
|
||||
local dist = vector.distance(obj.pos, pos)
|
||||
if (not closest_dist) or (dist < closest_dist) then
|
||||
closest_dist = dist
|
||||
end
|
||||
end
|
||||
|
||||
if closest_dist > obj.deletion_distance then
|
||||
obj:delete()
|
||||
return
|
||||
end
|
||||
if (closest_dist > obj.render_distance) and obj.rendered then
|
||||
obj:unrender()
|
||||
elseif (closest_dist < obj.render_distance*.85) and not obj.rendered then
|
||||
obj:render()
|
||||
end
|
||||
obj:update(dt)
|
||||
end
|
||||
end
|
||||
end)
|
||||
Guns4d.bullet_hole = Instantiatable_class:inherit({
|
||||
texture = 'bullet_hole.png',
|
||||
exp_time = 30, --how much time a rendered bullet hole takes to expire
|
||||
unrendered_exptime = 10, --how much time an unrendered bullet hole takes to expire
|
||||
deletion_distance = 100,
|
||||
--heat_effect = false,
|
||||
instances = {},
|
||||
size = .15,
|
||||
render_distance = 25,
|
||||
particle_spawner_id = nil,
|
||||
hole_entity = "guns4d:bullet_hole",
|
||||
rendered = true,
|
||||
})
|
||||
local Bullet_hole = Guns4d.bullet_hole
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Bullet_hole.construct(def)
|
||||
if def.instance then
|
||||
assert(def.pos)
|
||||
assert(def.rotation)
|
||||
--[[for i, v in pairs(def.pos) do
|
||||
if math.abs(v-Guns4d.math.round(v)) > (.5-(def.size/2)) then
|
||||
def.pos[i] = Guns4d.math.round(v)+((math.abs(v-Guns4d.math.round(v))/(v-Guns4d.math.round(v)))*(.5-(def.size/2)))
|
||||
end
|
||||
end]]
|
||||
Bullet_hole.instances[(#Bullet_hole.instances)+1]=def
|
||||
def.id = #Bullet_hole.instances
|
||||
def.unrendered_expire_speed = def.exp_time/def.unrendered_exptime
|
||||
def:render()
|
||||
end
|
||||
end
|
||||
function Bullet_hole:render()
|
||||
if self.old_timer then
|
||||
--acount for the time lost.
|
||||
self.timer = self.old_timer-(self.unrendered_exptime-self.timer)
|
||||
assert(self.instance)
|
||||
self.rendered = true
|
||||
|
||||
local normal = vector.rotate(vector.new(0,0,1), self.rotation)
|
||||
local ent = minetest.add_entity(self.pos+(normal*(.001+math.random()/1000)), self.hole_entity)
|
||||
ent:set_rotation(vector.dir_to_rotation(normal))
|
||||
ent:set_properties({visual_size={x=self.size, y=self.size, z=0}})
|
||||
self.entity = ent
|
||||
local lua_ent = ent:get_luaentity()
|
||||
lua_ent.lua_instance = self
|
||||
|
||||
if self.particle_spawner_id then
|
||||
minetest.delete_particlespawner(self.particle_spawner_id)
|
||||
end
|
||||
end
|
||||
function Bullet_hole:unrender()
|
||||
self.old_timer = self.timer
|
||||
self.timer = self.unrendered_exptime
|
||||
minetest.add_particlespawner({
|
||||
pos = self.pos,
|
||||
amount = 1,
|
||||
time=0,
|
||||
exptime = self.unrendered_exptime,
|
||||
assert(self.instance)
|
||||
self.rendered = false
|
||||
|
||||
local normal = vector.rotate(vector.new(0,0,1), self.rotation)
|
||||
local time_left = self.exp_time/self.unrendered_expire_speed
|
||||
local number_of_particles = 2
|
||||
minetest.add_particle({
|
||||
size = self.size*10,
|
||||
texture = self.texture,
|
||||
expiration_time = 2*time_left/math.ceil(number_of_particles*time_left),
|
||||
pos = self.pos+(normal*.05)
|
||||
})
|
||||
self.particle_spawner_id = minetest.add_particlespawner({
|
||||
pos = self.pos+(normal*.05),
|
||||
amount = math.ceil(number_of_particles*time_left)*5, --multiply so it doesn't flash in and out of existence...
|
||||
time=time_left,
|
||||
exptime = time_left/math.ceil(number_of_particles*time_left),
|
||||
texture = {
|
||||
name = 'bullet_hole.png',
|
||||
alpha_tween = {1,0}
|
||||
name = self.texture,
|
||||
scale = self.size*10
|
||||
}
|
||||
})
|
||||
if self.entity:get_pos() then
|
||||
self.entity:remove()
|
||||
end
|
||||
end
|
||||
function Bullet_hole:update()
|
||||
function Bullet_hole:delete()
|
||||
assert(self.instance)
|
||||
Bullet_hole.instances[self.id] = false
|
||||
if self.entity:get_pos() then
|
||||
self.entity:remove()
|
||||
end
|
||||
if self.particle_spawner_id then
|
||||
minetest.delete_particlespawner(self.particle_spawner_id)
|
||||
end
|
||||
end
|
||||
function Bullet_hole:update_ent()
|
||||
function Bullet_hole:update(dt)
|
||||
assert(self.instance)
|
||||
if self.rendered then
|
||||
self.exp_time = self.exp_time-dt
|
||||
else
|
||||
self.exp_time = self.exp_time-(dt*self.unrendered_expire_speed)
|
||||
end
|
||||
if self.exp_time <= 0 then
|
||||
self:delete()
|
||||
end
|
||||
end
|
||||
minetest.register_entity("guns4d:bullet_hole", {
|
||||
initial_properties = {
|
||||
visual = "cube",
|
||||
visual_size = {x=.15, y=.15, z=0},
|
||||
visual_size = {x=.1, y=.1, z=0},
|
||||
pointable = false,
|
||||
static_save = false,
|
||||
use_texture_alpha = true,
|
||||
textures = {"blank.png", "blank.png", "blank.png", "blank.png", "bullet_hole.png", "blank.png"}
|
||||
},
|
||||
on_step = function(self, dtime)
|
||||
on_step = function(self, dt)
|
||||
if TICK % 50 then
|
||||
local class_inst = self.class_Inst
|
||||
if class_inst.timer < 30 then
|
||||
local lua_instance = self.lua_instance
|
||||
if lua_instance.exp_time <= 0 then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
if lua_instance.exp_time < 2.5 then
|
||||
local properties = self.object:get_properties()
|
||||
properties.textures[5] = 'bullet_hole.png^[opacity:'..(math.floor((12.75*tostring(self.timer/30)))*20)
|
||||
properties.textures[5] = lua_instance.texture..'^[opacity:'..(math.floor((12.75*tostring(lua_instance.exp_time/2.5)))*20)
|
||||
self.object:set_properties(properties)
|
||||
end
|
||||
end
|
||||
|
@ -3,8 +3,7 @@ local ray = {
|
||||
state = "free",
|
||||
--pos = pos,
|
||||
last_node = "",
|
||||
hole_entity = "guns4d:bullet_hole",
|
||||
normal = vector.new(),
|
||||
bullet_hole_class = Guns4d.bullet_hole,
|
||||
--last_dir
|
||||
--exit_direction = dir,
|
||||
--range_left = def.bullet.range,
|
||||
@ -45,6 +44,10 @@ local ray = {
|
||||
pass_sound_mixing_factor = Guns4d.config.default_pass_sound_mixing_factor, --determines the ratio to use based on energy
|
||||
damage = 0,
|
||||
energy = 0,
|
||||
spread_deviation = 1, --deviation of the standard distribution represented. The lower the closer to the center of the spread a pellet is more likely to be.
|
||||
spread = 0, --defaults to 1 if pellets present but spread not defined.
|
||||
pellets = 1,
|
||||
wall_penetration = true, --turns off by default if pellets are greater then one.
|
||||
ITERATION_DISTANCE = Guns4d.config.default_penetration_iteration_distance,
|
||||
}
|
||||
|
||||
@ -63,7 +66,7 @@ function ray:find_transverse_edge()
|
||||
return pointed.intersection_point, pointed.intersection_normal
|
||||
end
|
||||
end
|
||||
function ray:_cast()
|
||||
function ray:cast()
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local next_state = self.state --next state of course the state of the next ray.
|
||||
|
||||
@ -84,7 +87,7 @@ function ray:_cast()
|
||||
end_pos = self.pos+(self.dir*self.range)
|
||||
end
|
||||
--do the main raycast. We don't account for mmRHA dropoff here.
|
||||
local continue = true --indicates wether to :_iterate wether the Bullet_ray has ended
|
||||
local continue = true --indicates wether to :iterate wether the Bullet_ray has ended
|
||||
local cast = minetest.raycast(self.pos, end_pos, true, true)
|
||||
local edge_length
|
||||
if edge then
|
||||
@ -142,9 +145,9 @@ function ray:_cast()
|
||||
return pointed_node, pointed_object, next_state, end_pos, end_normal, continue
|
||||
end
|
||||
--the main function.
|
||||
function ray:_iterate(initialized)
|
||||
function ray:iterate()
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local pointed_node, pointed_object, next_state, end_pos, end_normal, continue = self:_cast()
|
||||
local pointed_node, pointed_object, next_state, end_pos, end_normal, continue = self:cast()
|
||||
|
||||
local distance = vector.distance(self.pos, end_pos)
|
||||
if self.state == "free" then
|
||||
@ -163,7 +166,7 @@ function ray:_iterate(initialized)
|
||||
--calc penetration loss from traveling through the block
|
||||
local penetration_loss = distance*Guns4d.node_properties[self.last_node_name].mmRHA
|
||||
--calculate our energy loss based on the percentage of energy our penetration represents.
|
||||
self.energy = self.energy-((self.init_energy*self.energy_sharp_ratio)*(penetration_loss/self.sharp_penetration))
|
||||
self.energy = self.energy-((self.init_def.energy*self.energy_sharp_ratio)*(penetration_loss/self.sharp_penetration))
|
||||
end
|
||||
--set values for next iteration.
|
||||
self.range = self.range-distance
|
||||
@ -191,7 +194,7 @@ function ray:_iterate(initialized)
|
||||
normal = end_normal, --end normal may be nil, as it's only for hit effects.
|
||||
})
|
||||
if continue and self.range > 0 and self.energy > 0 then
|
||||
self:_iterate(true)
|
||||
self:iterate()
|
||||
end
|
||||
--[[if not initialized then
|
||||
for i, v in pairs(self.history) do
|
||||
@ -220,7 +223,7 @@ function ray:hit_entity(object)
|
||||
|
||||
local resistance = object:get_armor_groups() -- support for different body parts is needed here, that's for... a later date, though.
|
||||
--calculate the amount of penetration we've lost based on how much of the energy is converted to penetration (energy_sharp_ratio)
|
||||
local dropoff_ratio = (1-(self.energy/self.init_energy))
|
||||
local dropoff_ratio = (1-(self.energy/self.init_def.energy))
|
||||
local bullet_sharp_pen = self.sharp_penetration-(self.sharp_penetration*dropoff_ratio*self.energy_sharp_ratio)
|
||||
local effective_sharp_pen = Guns4d.math.clamp(bullet_sharp_pen - (resistance.guns4d_mmRHA or 0), 0, math.huge)
|
||||
local converted_Pa = (bullet_sharp_pen-effective_sharp_pen) * self.sharp_to_blunt_conversion_factor
|
||||
@ -265,14 +268,14 @@ function ray:apply_damage(object, sharp_pen, blunt_pen)
|
||||
end
|
||||
function ray:bullet_hole(pos, normal)
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local nearby_players = false
|
||||
--[[local nearby_players = false
|
||||
for pname, player in pairs(minetest.get_connected_players()) do
|
||||
if vector.distance(player:get_pos(), pos) < 50 then
|
||||
nearby_players = true; break
|
||||
end
|
||||
end
|
||||
end]]
|
||||
--if it's close enough to any players, then add it
|
||||
if nearby_players then
|
||||
--[[if nearby_players then
|
||||
--this entity will keep track of itself.
|
||||
local ent = minetest.add_entity(pos+(normal*(.0001+math.random()/1000)), self.hole_entity)
|
||||
ent:set_rotation(vector.dir_to_rotation(normal))
|
||||
@ -280,18 +283,23 @@ function ray:bullet_hole(pos, normal)
|
||||
lua_ent.block_pos = pos
|
||||
else
|
||||
Guns4d.effects.spawn_bullet_hole_particle(pos, self.hole_scale, '(bullet_hole_1.png^(bullet_hole_2.png^[opacity:129))')
|
||||
end
|
||||
end]]
|
||||
local bullet_hole = self.bullet_hole_class:new({
|
||||
pos = vector.new(pos),
|
||||
rotation = vector.dir_to_rotation(normal)
|
||||
})
|
||||
-- ent:set_rotation(vector.dir_to_rotation(normal))
|
||||
end
|
||||
function ray:play_bullet_pass_sounds()
|
||||
--iteration done, damage applied, find players to apply bullet whizz to
|
||||
local start_pos = self.init_pos
|
||||
local start_pos = self.init_def.pos
|
||||
local played_for = {}
|
||||
for i = #self.history, 1, -1 do
|
||||
local v = self.history[i]
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
if (player~=self.player) and not played_for[player] then
|
||||
local pos = player:get_pos()+vector.new(0,player:get_properties().eye_height,0)
|
||||
local nearest = Guns4d.nearest_point_on_line(start_pos, v.pos, pos)
|
||||
local nearest = Guns4d.math.nearest_point_on_line(start_pos, v.pos, pos)
|
||||
if vector.distance(nearest, pos) < self.pass_sound_max_distance then
|
||||
played_for[player] = true
|
||||
if self.pass_sounds[1] then
|
||||
@ -301,7 +309,7 @@ function ray:play_bullet_pass_sounds()
|
||||
else
|
||||
--interpolate to find the energy of the shot to determine supersonic or not.
|
||||
local v1
|
||||
if #self.history > i then v1 = v[i+1].energy else v1 = self.init_energy end
|
||||
if #self.history > i then v1 = v[i+1].energy else v1 = self.init_def.energy end
|
||||
local v2 = v.energy
|
||||
|
||||
local ip_r = vector.distance(start_pos, nearest)/vector.distance(start_pos, pos)
|
||||
@ -348,6 +356,32 @@ function ray:play_bullet_pass_sounds()
|
||||
start_pos = v.pos
|
||||
end
|
||||
end
|
||||
function ray:simple_cast(pos, dir)
|
||||
local cast = minetest.raycast(pos, pos+(dir*self.range), true, true)
|
||||
local pointed
|
||||
for hit in cast do
|
||||
if hit.type == "node" and Guns4d.node_properties[minetest.get_node(hit.under).name].behavior ~= "ignore" then
|
||||
self:bullet_hole(hit.intersection_point, hit.intersection_normal)
|
||||
pointed = hit
|
||||
break
|
||||
end
|
||||
end
|
||||
if pointed then
|
||||
table.insert(self.history, {
|
||||
pos = pointed.intersection_pos,
|
||||
energy = self.energy, --TODO, ENERGY CALCS
|
||||
state = "free",
|
||||
last_node = (pointed.under and minetest.get_node(pointed.under).name),
|
||||
normal = pointed.intersection_normal, --end normal may be nil, as it's only for hit effects.
|
||||
})
|
||||
else
|
||||
table.insert(self.history, {
|
||||
pos = pos+(dir*self.range),
|
||||
energy = self.energy, --TODO, ENERGY CALCS
|
||||
state = "free",
|
||||
})
|
||||
end
|
||||
end
|
||||
function ray.construct(def)
|
||||
if def.instance then
|
||||
--these asserts aren't necessary, probably drags down performance a tiny bit.
|
||||
@ -379,15 +413,40 @@ function ray.construct(def)
|
||||
end
|
||||
def.energy_sharp_ratio = (def.energy-def.blunt_penetration)/def.energy
|
||||
|
||||
def.init_energy = def.energy
|
||||
def.init_def = {
|
||||
energy = def.energy,
|
||||
pos = def.pos,
|
||||
dir = def.dir or def.gun:get_dir(),
|
||||
range = def.range
|
||||
}
|
||||
if def.pellets > 1 then
|
||||
if rawget(def, "wall_penetration") == nil then
|
||||
def.wall_penetration = false
|
||||
end
|
||||
if rawget(def, "spread") == nil then
|
||||
def.spread = 1
|
||||
end
|
||||
end
|
||||
--blunt pen is in the same units (1 Joule/Area^3 = 1 Pa), so we use it to make the ratio by subtraction.
|
||||
|
||||
def.dir = vector.new(def.dir)
|
||||
def.pos = vector.new(def.pos)
|
||||
def.history = {}
|
||||
def.init_pos = vector.new(def.pos) --has to be cloned before iteration
|
||||
def:_iterate()
|
||||
def:play_bullet_pass_sounds()
|
||||
local init_def = def.init_def
|
||||
for i=1,def.pellets do
|
||||
local x, y = Guns4d.math.angular_normal_distribution(def.spread_deviation)
|
||||
--x, y = (math.random()-.5)*2, (math.random()-.5)*2
|
||||
local dir = def.gun:get_dir(false, x*def.spread, y*def.spread)
|
||||
if def.wall_penetration then
|
||||
def.energy = init_def.energy
|
||||
def.dir = dir or vector.new(init_def.dir)
|
||||
def.pos = vector.new(init_def.pos)
|
||||
def.range = init_def.range
|
||||
def.history = {}
|
||||
def:iterate()
|
||||
def:play_bullet_pass_sounds()
|
||||
else
|
||||
def.history = {} --we still have to use this for the pass sounds
|
||||
def:simple_cast(init_def.pos, dir or init_def.dir)
|
||||
def:play_bullet_pass_sounds()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Guns4d.bullet_ray = Instantiatable_class:inherit(ray)
|
@ -12,6 +12,9 @@ Guns4d.control_handler = {
|
||||
loop = false,
|
||||
func=function(active, interrupted, data, busy_controls)
|
||||
}
|
||||
on_use = function()
|
||||
on_secondary_use = function()
|
||||
on_drop = function() return a bool to indicate wether to drop the item or not.
|
||||
}
|
||||
]]
|
||||
ads = false,
|
||||
@ -114,6 +117,12 @@ function controls:on_use(itemstack, pointed_thing)
|
||||
actions.on_use(itemstack, self.handler, pointed_thing)
|
||||
end
|
||||
end
|
||||
function controls:on_drop(itemstack, pointed_thing, pos)
|
||||
local actions = self:get_actions()
|
||||
if actions.on_drop then
|
||||
return actions.on_use(itemstack, self.handler, pos)
|
||||
end
|
||||
end
|
||||
function controls:on_secondary_use(itemstack, pointed_thing)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local actions = self:get_actions()
|
||||
|
@ -30,7 +30,7 @@ end
|
||||
local function render_length(rotation, fov)
|
||||
local dir = vector.rotate({x=0,y=0,z=1}, {x=rotation.x*math.pi/180,y=0,z=0})
|
||||
vector.rotate(dir,{x=0,y=rotation.y*math.pi/180,z=0})
|
||||
local out = Guns4d.rltv_point_to_hud(dir, fov, 1)
|
||||
local out = Guns4d.math.rltv_point_to_hud(dir, fov, 1)
|
||||
return math.sqrt(out.x^2+out.y^2)
|
||||
end
|
||||
function Dynamic_crosshair:update(dt)
|
||||
@ -85,7 +85,7 @@ function Dynamic_crosshair:update(dt)
|
||||
|
||||
|
||||
--now figure out what frame will be our correct spread
|
||||
local offset = Guns4d.rltv_point_to_hud(dir, fov, 1) --pretend it's a 1:1 ratio so we can do things correctly.
|
||||
local offset = Guns4d.math.rltv_point_to_hud(dir, fov, 1) --pretend it's a 1:1 ratio so we can do things correctly.
|
||||
local length = math.sqrt(offset.x^2+offset.y^2) --get the max length.
|
||||
|
||||
local img_perc = (self.scale*2*handler.wininfo.real_hud_scaling*self.width)/handler.wininfo.size.x --the percentage that the hud element takes up
|
||||
|
137
classes/Gun.lua
137
classes/Gun.lua
@ -125,7 +125,9 @@ local gun_default = {
|
||||
charging = { --how the gun "cocks"
|
||||
require_draw_on_swap = true,
|
||||
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.
|
||||
default_draw_time = 1,
|
||||
draw_time = 1,
|
||||
draw_animation = "draw",
|
||||
draw_sound = "draw"
|
||||
--sound = soundspec
|
||||
},
|
||||
reload = { --used by defualt controls. Still provides usefulness elsewhere.
|
||||
@ -135,12 +137,14 @@ local gun_default = {
|
||||
},
|
||||
ammo = { --used by ammo_handler
|
||||
magazine_only = false,
|
||||
--capacity = 0, --this is only needed if magazine_only = false
|
||||
accepted_bullets = {},
|
||||
accepted_magazines = {},
|
||||
initial_mag = "empty"
|
||||
},
|
||||
visuals = {
|
||||
--mesh
|
||||
--textures = {},
|
||||
--mesh="string.b3d",
|
||||
backface_culling = true,
|
||||
root = "gun",
|
||||
magazine = "magazine",
|
||||
@ -252,8 +256,6 @@ local gun_default = {
|
||||
consts = {
|
||||
HIP_PLAYER_GUN_ROT_RATIO = .75,
|
||||
AIM_OUT_AIM_IN_SPEED_RATIO = 2.5,
|
||||
HIPFIRE_BONE = "guns3d_hipfire_bone", --these shouldn't be here at all, these need to be model determinant.
|
||||
AIMING_BONE = "guns3d_aiming_bone",
|
||||
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,
|
||||
@ -266,7 +268,10 @@ local gun_default = {
|
||||
HAS_WAG = true,
|
||||
HAS_GUN_AXIAL_OFFSETS = true,
|
||||
ANIMATIONS_OFFSET_AIM = false,
|
||||
LOOP_IDLE_ANIM = false
|
||||
LOOP_IDLE_ANIM = false,
|
||||
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, .1,.2,.2},
|
||||
},
|
||||
--[[animation_data = { --where animations data is stored.
|
||||
anim_runtime = 0,
|
||||
@ -295,17 +300,15 @@ end
|
||||
function gun_default:draw()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local props = self.properties
|
||||
if props.visuals.animations.draw then
|
||||
self:set_animation(props.visuals.animations.draw, props.charging.default_draw_time)
|
||||
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.draw then
|
||||
local sounds = Guns4d.table.deep_copy(props.sounds.draw)
|
||||
sounds.player = self.player
|
||||
sounds.pos = self.pos
|
||||
Guns4d.play_sounds(sounds)
|
||||
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.default_draw_time
|
||||
self.rechamber_time = props.charging.draw_time
|
||||
end
|
||||
--update gun, the main function.
|
||||
function gun_default:update(dt)
|
||||
@ -442,7 +445,7 @@ function gun_default:attempt_fire()
|
||||
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),
|
||||
dir = dir,
|
||||
--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)
|
||||
@ -455,7 +458,7 @@ function gun_default:attempt_fire()
|
||||
--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.
|
||||
fire_sound.pos = self.pos
|
||||
Guns4d.play_sounds(fire_sound)
|
||||
self:play_sounds(fire_sound)
|
||||
|
||||
self.rechamber_time = 60/self.properties.firerateRPM
|
||||
return true
|
||||
@ -516,37 +519,72 @@ function gun_default:get_player_axial_dir(rltv)
|
||||
end, hud)]]
|
||||
return dir
|
||||
end
|
||||
function gun_default:get_dir(rltv)
|
||||
--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_offset_rotation
|
||||
local handler = self.handler
|
||||
--rotate x and then y.
|
||||
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.gun_axial.x+rotation.player_axial.x)*math.pi/180), z=0}))
|
||||
dir = Vec.rotate(dir, {y=((rotation.gun_axial.y+rotation.player_axial.y)*math.pi/180), x=0, z=0})
|
||||
if not rltv then
|
||||
--old code. I used a site (symbolab.com) to precalculate the rotation matrices to save on performance since spread has to run this.
|
||||
--local dir = Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.gun_axial.x+rotation.player_axial.x)*math.pi/180), z=0})
|
||||
--dir = Vec.rotate(dir, {y=((rotation.gun_axial.y+rotation.player_axial.y)*math.pi/180), x=0, z=0})
|
||||
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 --look rotation is that actual rotation of the player's camera, player_rotation is the gun's current rotation (because vertical rotation will differ for smoothness.)
|
||||
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})
|
||||
--dir = Vec.rotate(dir, {x=-handler.look_rotation.x*math.pi/180,y=-handler.look_rotation.y*math.pi/180,z=0})
|
||||
--[[
|
||||
Cy = math.cos(y)
|
||||
Sy = math.sin(y)
|
||||
Cp = math.cos(p)
|
||||
Sp = math.sin(p)
|
||||
dir.x = (Cy*dir.x)+(Sy*Sp*dir.y)+(Sy*Cp*dir.z)
|
||||
dir.y = (dir.y*Cp)-(dir.z*Sp)
|
||||
dir.z = -(dir.x*Sy)+(dir.y*Sp*Cy)+(dir.z*Cy*Cp)]]
|
||||
p = handler.look_rotation.x*math.pi/180
|
||||
y = handler.look_rotation.y*math.pi/180
|
||||
else
|
||||
dir = Vec.rotate(dir, {x=self.player_rotation.x*math.pi/180,y=self.player_rotation.y*math.pi/180,z=0})
|
||||
p = -self.player_rotation.x*math.pi/180
|
||||
y = -self.player_rotation.y*math.pi/180
|
||||
--dir = Vec.rotate(dir, {x=self.player_rotation.x*math.pi/180,y=self.player_rotation.y*math.pi/180,z=0})
|
||||
end
|
||||
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
|
||||
|
||||
--local hud_pos = dir+player:get_pos()+{x=0,y=player:get_properties().eye_height,z=0}+vector.rotate(player:get_eye_offset()/10, {x=0,y=player_rotation.y*math.pi/180,z=0})
|
||||
--[[local hud = player: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)
|
||||
player:hud_remove(hud)
|
||||
end, hud)]]
|
||||
return dir
|
||||
end
|
||||
|
||||
--some old debug code for get_dir
|
||||
--local hud_pos = dir+player:get_pos()+{x=0,y=player:get_properties().eye_height,z=0}+vector.rotate(player:get_eye_offset()/10, {x=0,y=player_rotation.y*math.pi/180,z=0})
|
||||
--[[local hud = player: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)
|
||||
player:hud_remove(hud)
|
||||
end, hud)]]
|
||||
--broken! doesn't properly reflect values.
|
||||
function gun_default:get_pos(added_pos, relative, debug)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
@ -742,7 +780,36 @@ function gun_default:clear_animation()
|
||||
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.
|
||||
|
@ -63,12 +63,12 @@ function Sprite_scope:update()
|
||||
dir = dir + (self.gun.properties.ads.offset+vector.new(self.gun.properties.ads.horizontal_offset,0,0))*0
|
||||
end
|
||||
local fov = self.player:get_fov()
|
||||
local real_aim = Guns4d.rltv_point_to_hud(dir, fov, ratio)
|
||||
local anim_aim = Guns4d.rltv_point_to_hud(vector.rotate({x=0,y=0,z=1}, self.gun.animation_rotation*math.pi/180), fov, ratio)
|
||||
local real_aim = Guns4d.math.rltv_point_to_hud(dir, fov, ratio)
|
||||
local anim_aim = Guns4d.math.rltv_point_to_hud(vector.rotate({x=0,y=0,z=1}, self.gun.animation_rotation*math.pi/180), fov, ratio)
|
||||
real_aim.x = real_aim.x+anim_aim.x; real_aim.y = real_aim.y+anim_aim.y
|
||||
|
||||
--print(dump(self.gun.animation_rotation))
|
||||
local paxial_aim = Guns4d.rltv_point_to_hud(self.gun.local_paxial_dir, fov, ratio)
|
||||
local paxial_aim = Guns4d.math.rltv_point_to_hud(self.gun.local_paxial_dir, fov, ratio)
|
||||
--so custom scopes can do their thing without doing more calcs
|
||||
self.hud_projection_real = real_aim
|
||||
self.hud_projection_paxial = paxial_aim
|
||||
|
@ -198,46 +198,60 @@ local function initialize_b3d_animation_data(self, props)
|
||||
end]]
|
||||
--print()
|
||||
end
|
||||
local function complete_item(self, props)
|
||||
local function reregister_item(self, props)
|
||||
assert(self.itemstring, "no itemstring provided. Cannot create a gun without an associated itemstring.")
|
||||
local item_def = minetest.registered_items[self.itemstring]
|
||||
assert(rawget(self, "name"), "no name provided in new class")
|
||||
assert(rawget(self, "itemstring"), "no itemstring provided in new class")
|
||||
assert(not((props.ammo.capacity) and (not props.ammo.magazine_only)), "gun does not accept magazines, but has no set capcity! Please define ammo.capacity")
|
||||
assert(props.ammo.capacity or props.ammo.magazine_only, "gun does not accept magazines, but has no set capcity! Please define ammo.capacity")
|
||||
assert(item_def, self.itemstring.." : item is not registered.")
|
||||
|
||||
--override methods so control handler can do it's job
|
||||
local old_on_use = item_def.on_use
|
||||
local old_on_s_use = item_def.on_secondary_use
|
||||
local old_on_drop = item_def.on_drop
|
||||
self.properties.inventory_image = item_def.inventory_image
|
||||
--override the item to hook in controls. (on_drop needed)
|
||||
minetest.override_item(self.itemstring, {
|
||||
on_use = function(itemstack, user, pointed_thing)
|
||||
if old_on_use then
|
||||
old_on_use(itemstack, user, pointed_thing)
|
||||
end
|
||||
Guns4d.players[user:get_player_name()].control_handler:on_use(itemstack, pointed_thing)
|
||||
if old_on_use then
|
||||
return old_on_use(itemstack, user, pointed_thing)
|
||||
end
|
||||
end,
|
||||
on_secondary_use = function(itemstack, user, pointed_thing)
|
||||
if old_on_s_use then
|
||||
old_on_s_use(itemstack, user, pointed_thing)
|
||||
end
|
||||
Guns4d.players[user:get_player_name()].control_handler:on_secondary_use(itemstack, pointed_thing)
|
||||
if old_on_s_use then
|
||||
return old_on_s_use(itemstack, user, pointed_thing)
|
||||
end
|
||||
end,
|
||||
on_drop = function(itemstack, user, pos)
|
||||
local cancel_drop = Guns4d.players[user:get_player_name()].control_handler:on_drop(itemstack)
|
||||
if (not cancel_drop) and old_on_drop then
|
||||
return old_on_drop(itemstack, user, pos)
|
||||
end
|
||||
end
|
||||
})
|
||||
Guns4d.register_item(self.itemstring, {
|
||||
collisionbox = self.consts.ITEM_COLLISIONBOX,
|
||||
selectionbox = self.consts.ITEM_SELECTIONBOX,
|
||||
mesh = self.properties.visuals.mesh,
|
||||
textures = self.properties.visuals.textures,
|
||||
animation = self.properties.visuals.animations.loaded
|
||||
})
|
||||
end
|
||||
local function create_visual_entity(self,props)
|
||||
minetest.register_entity(self.name.."_visual", {
|
||||
local function create_visual_entity(def, props)
|
||||
minetest.register_entity(def.name.."_visual", {
|
||||
initial_properties = {
|
||||
visual = "mesh",
|
||||
mesh = props.visuals.mesh,
|
||||
textures = props.textures,
|
||||
textures = props.visuals.textures,
|
||||
glow = 0,
|
||||
pointable = false,
|
||||
static_save = false,
|
||||
backface_culling = props.visuals.backface_culling
|
||||
},
|
||||
on_step = function(self, dtime)
|
||||
on_step = --[[def.entity_function or]] function(self)
|
||||
local obj = self.object
|
||||
if not self.parent_player then obj:remove() return end
|
||||
local player = self.parent_player
|
||||
@ -258,11 +272,11 @@ local function create_visual_entity(self,props)
|
||||
end
|
||||
if handler.control_handler.ads then
|
||||
local normal_pos = (props.ads.offset)*10
|
||||
obj:set_attach(player, lua_object.consts.AIMING_BONE, normal_pos, -axial_rot, visibility)
|
||||
obj:set_attach(player, handler.player_model_handler.bone_names.aim, normal_pos, -axial_rot, visibility)
|
||||
else
|
||||
local normal_pos = vector.new(props.hip.offset)*10
|
||||
-- vector.multiply({x=normal_pos.x, y=normal_pos.z, z=-normal_pos.y}, 10)
|
||||
obj:set_attach(player, lua_object.consts.HIPFIRE_BONE, normal_pos, -axial_rot, visibility)
|
||||
obj:set_attach(player, handler.player_model_handler.bone_names.hipfire, normal_pos, -axial_rot, visibility)
|
||||
end
|
||||
end,
|
||||
})
|
||||
@ -282,7 +296,7 @@ function gun_default:construct_base_class()
|
||||
|
||||
-- if it's not a template, then create an item, override some props
|
||||
if self.name ~= "__template" then
|
||||
complete_item(self, props)
|
||||
reregister_item(self, props)
|
||||
end
|
||||
--create sets. This may need to be put in instances of modifications can change accepted ammos
|
||||
self.accepted_bullets = {}
|
||||
|
@ -67,7 +67,7 @@ end
|
||||
local reload_actions = {}
|
||||
function Guns4d.default_controls.register_reloading_state_type(name, def)
|
||||
assert(type(def)=="table", "improper definition type")
|
||||
assert(type(def.on_completion)=="function", "action has no completion function")
|
||||
assert(type(def.on_completion)=="function", "action has no completion function") --return a bool (or nil) indicating wether to progress. Nil returns the function (breaking out of the reload cycle.)
|
||||
assert(type(def.validation_check)=="function") --return bool indicating wether it is valid. If nil it is assumed to be valid
|
||||
reload_actions[name] = def
|
||||
end
|
||||
@ -92,21 +92,21 @@ reg_mstate("unload_mag", {
|
||||
|
||||
reg_mstate("store", {
|
||||
on_completion = function(gun, ammo_handler, next_state)
|
||||
local pause = false
|
||||
--[[local pause = false
|
||||
--needs to happen before so we don't detect the ammo we just unloaded
|
||||
if not ammo_handler:inventory_has_ammo() then
|
||||
pause=true
|
||||
end
|
||||
end]]
|
||||
if gun.properties.ammo.magazine_only and (ammo_handler.ammo.loaded_mag ~= "empty") then
|
||||
ammo_handler:unload_magazine()
|
||||
else
|
||||
ammo_handler:unload_all()
|
||||
end
|
||||
--if there's no ammo make hold so you don't reload the same ammo you just unloaded.
|
||||
if pause then
|
||||
--[[if pause then
|
||||
return
|
||||
end
|
||||
return true
|
||||
return true]]
|
||||
end,
|
||||
validation_check = function(gun, ammo_handler, next_state)
|
||||
if gun.properties.ammo.magazine_only and (ammo_handler.ammo.loaded_mag == "empty") then
|
||||
@ -150,7 +150,19 @@ reg_mstate("load", {
|
||||
return true
|
||||
end
|
||||
})
|
||||
|
||||
reg_mstate("load_cartridge", {
|
||||
on_completion = function(gun, ammo_handler, next_state)
|
||||
return not ammo_handler:load_single_cartridge() --it returns wether the cartidge could be loaded
|
||||
end,
|
||||
validation_check = function(gun, ammo_handler, next_state)
|
||||
if (ammo_handler.ammo.total_bullets<gun.properties.ammo.capacity) and ammo_handler:inventory_has_ammo(true) then
|
||||
minetest.chat_send_all(dump((ammo_handler:inventory_has_ammo(true))))
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
})
|
||||
reg_mstate("charge", {
|
||||
on_completion = function(gun, ammo_handler)
|
||||
ammo_handler:chamber_round()
|
||||
@ -159,8 +171,9 @@ reg_mstate("charge", {
|
||||
validation_check = function(gun, ammo_handler, next_state)
|
||||
if (ammo_handler.ammo.next_bullet ~= "empty") or (ammo_handler.ammo.total_bullets == 0) then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end
|
||||
})
|
||||
Guns4d.default_controls.reload = {
|
||||
@ -259,11 +272,10 @@ Guns4d.default_controls.reload = {
|
||||
if type(next_state.sounds) == "table" then
|
||||
sounds = Guns4d.table.deep_copy(props.reload[next_state_index].sounds)
|
||||
elseif type(next_state.sounds) == "string" then
|
||||
sounds = assert(props.sounds[next_state.sounds])
|
||||
sounds = Guns4d.table.deep_copy(assert(props.sounds[next_state.sounds], "no sound by the name of "..next_state.sounds))
|
||||
end
|
||||
sounds.pos = gun.pos
|
||||
sounds.max_hear_distance = sounds.max_hear_distance or gun.consts.DEFAULT_MAX_HEAR_DISTANCE
|
||||
data.played_sounds = Guns4d.play_sounds(sounds)
|
||||
data.played_sounds = {gun:play_sounds(sounds)}
|
||||
end
|
||||
--print(dump(next_state_index))
|
||||
--end
|
||||
|
10
init.lua
10
init.lua
@ -11,7 +11,7 @@ Guns4d.config = {
|
||||
show_gun_inv_ammo_count = true,
|
||||
control_hybrid_toggle_threshold = .3,
|
||||
control_held_toggle_threshold = 0,
|
||||
empty_symbol = "0e",
|
||||
empty_symbol = "E",
|
||||
default_damage_group = "fleshy",
|
||||
infinite_ammo_priv = "guns4d_infinite_ammo",
|
||||
interpret_initial_wear_as_ammo = false,
|
||||
@ -23,15 +23,19 @@ Guns4d.config = {
|
||||
headshot_damage_factor = 1.75,
|
||||
enable_touchscreen_command_name = "guns4d_enable_touchmode",
|
||||
minimum_supersonic_energy_assumption = 900, --used to determine the energy of a "supersonic" bullet for bullet whizzing sound effects
|
||||
default_audio_attenuation_rate = .8, --changes the dropoff rate of sound. Acts as a multiplier for the distance used to calculate inverse square law. Most guns (from the gun packs) set their own, so this is mainly for reloads.
|
||||
mix_supersonic_and_subsonic_sounds = true,
|
||||
default_pass_sound_mixing_factor = 10,
|
||||
third_person_gain_multiplier = 1/3,
|
||||
default_penetration_iteration_distance = .25,
|
||||
maximum_bullet_holes = 20,
|
||||
--enable_assert = false,
|
||||
realistic_items = true
|
||||
--`["official_content.replace_ads_with_bloom"] = false,
|
||||
--`["official_content.uses_magazines"] = true
|
||||
}
|
||||
local path = minetest.get_modpath("guns4d")
|
||||
|
||||
print("file read?")
|
||||
local conf = Settings(path.."/guns4d_settings.conf"):to_table() or {}
|
||||
local mt_conf = minetest.settings:to_table() --allow use of MT config for servers that regularly update 4dguns through it's development
|
||||
for i, v in pairs(Guns4d.config) do
|
||||
@ -44,6 +48,7 @@ for i, v in pairs(Guns4d.config) do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
dofile(path.."/infinite_ammo.lua")
|
||||
dofile(path.."/misc_helpers.lua")
|
||||
dofile(path.."/item_entities.lua")
|
||||
@ -55,6 +60,7 @@ dofile(path.."/block_values.lua")
|
||||
dofile(path.."/ammo_api.lua")
|
||||
path = path .. "/classes"
|
||||
dofile(path.."/Instantiatable_class.lua")
|
||||
dofile(path.."/Bullet_hole.lua")
|
||||
dofile(path.."/Bullet_ray.lua")
|
||||
dofile(path.."/Control_handler.lua")
|
||||
dofile(path.."/Ammo_handler.lua")
|
||||
|
@ -33,7 +33,11 @@ local defaults = {
|
||||
--light_source = 0,
|
||||
collisionbox_size = 2,
|
||||
visual_size = 1,
|
||||
offset = {x=0,y=0,z=0}
|
||||
realistic = Guns4d.config.realistic_items,
|
||||
backface_culling = false,
|
||||
--animation = {x=0,y=0, speed=15, loop=true, blend=nil},
|
||||
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
|
||||
@ -43,10 +47,6 @@ 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")
|
||||
def = Guns4d.table.fill(defaults, def)
|
||||
if not def.selectionbox then
|
||||
def.selectionbox = vector.new(def.collisionbox_size, def.collisionbox_size, def.collisionbox_size)
|
||||
end
|
||||
def.offset = vector.new(def.offset)
|
||||
Guns4d.registered_items[itemstring] = def
|
||||
end
|
||||
|
||||
@ -63,60 +63,80 @@ def.set_item = function(self, item)
|
||||
end
|
||||
|
||||
local item_def = Guns4d.registered_items[stack:get_name()]
|
||||
local cbox
|
||||
local sbox
|
||||
local a = item_def.collisionbox_size
|
||||
--[[local a = item_def.collisionbox_size
|
||||
local o = item_def.collisionbox_offset
|
||||
local b = item_def.selectionbox
|
||||
if item_def.realistic == true then
|
||||
cbox = {-a/20, 0, -a/20, a/20, (a*2)/20, a/20} --we want the collision_box to sit above it.
|
||||
sbox = {-b.x/20, 0, -b.z/20, b.x/20, b.y/10, b.z/20, rotate=true}
|
||||
cbox = {(-a-o.x)/20, 0-(o.y/20), (-a-o.z)/20, (a-o.x)/20, (a-o.y)/10, (a-o.z)/20} --we want the collision_box to sit above it.
|
||||
sbox = {(-b.x-o.x)/20, (-b.y/20), (-b.z-o.z)/20, (b.x-o.x)/20, (b.y/20), (b.z-o.z)/20, rotate=true}
|
||||
else
|
||||
cbox = {-a/20, -a/20, -a/20, a/20, a/20, a/20}
|
||||
sbox = {-b.x/20, -b.y/20, -b.z/20, b.x/20, b.y/20, b.z/20}
|
||||
end
|
||||
cbox = {(-a-o.x)/20, (-a-o.y)/20, (-a-o.z)/20, (a-o.x)/20, (a-o.y)/20, (a-o.z)/20}
|
||||
sbox = {(-b.x-o.x)/20, (-b.y-o.y)/20, (-b.z-o.z)/20, (b.x-o.x)/20, (b.y-o.y)/20, (b.z-o.z)/20}
|
||||
end]]
|
||||
local cbox = item_def.collisionbox
|
||||
local sbox = item_def.selectionbox
|
||||
self.object:set_properties({
|
||||
is_visible = true,
|
||||
visual = "mesh",
|
||||
mesh = item_def.mesh,
|
||||
textures = item_def.textures,
|
||||
collisionbox = cbox,
|
||||
selectionbox = sbox,
|
||||
selectionbox = {sbox[1], sbox[2], sbox[3], sbox[4], sbox[5], sbox[6], rotate=true},
|
||||
glow = item_def and item_def.light_source and math.floor(def.light_source/2+0.5),
|
||||
backface_culling = item_def.backface_culling,
|
||||
visual_size = {x=item_def.visual_size,y=item_def.visual_size,z=item_def.visual_size},
|
||||
automatic_rotate = (not item_def.realistic) and math.pi * 0.5 * 0.2 / a,
|
||||
automatic_rotate = ((not item_def.realistic) and math.pi * 0.5 * 0.2 / 5) or nil,
|
||||
infotext = stack:get_description(),
|
||||
})
|
||||
self._collisionbox = cbox
|
||||
--self._collisionbox = cbox
|
||||
end
|
||||
local old = def.on_step
|
||||
def._respawn = function(self)
|
||||
minetest.add_item(self.object:get_pos(), self.itemstring)
|
||||
end
|
||||
def.on_step = function(self, dt, mr, ...)
|
||||
old(self, dt, mr, ...)
|
||||
--icky nesting.
|
||||
local item_def
|
||||
if not self._guns4d_animation_set then
|
||||
item_def = Guns4d.registered_items[ItemStack(self.itemstring):get_name()]
|
||||
if item_def then
|
||||
local anim = item_def.animation
|
||||
if anim then
|
||||
self.object:set_animation({x=anim.x, y=anim.y}, anim.speed, anim.blend, anim.loop)
|
||||
end
|
||||
else
|
||||
self:_respawn()
|
||||
end
|
||||
end
|
||||
if mr and mr.touching_ground then
|
||||
local item_def = Guns4d.registered_items[ItemStack(self.itemstring):get_name()]
|
||||
if item_def and not self._rotated then
|
||||
item_def = item_def or Guns4d.registered_items[ItemStack(self.itemstring):get_name()]
|
||||
if item_def and not self._4dguns_rotated then
|
||||
if item_def.realistic then
|
||||
self.object:set_properties({
|
||||
automatic_rotate = (not item_def.realistic) and math.pi * 0.5 * 0.2 / item_def.visual_size,
|
||||
automatic_rotate = nil
|
||||
})
|
||||
local rot = self.object:get_rotation()
|
||||
self.object:set_rotation({y=rot.y, x=rot.x+(math.pi/2), z=0})
|
||||
self._rotated = true
|
||||
self.object:set_rotation({y=rot.y, x=rot.x, z=math.pi*.5})
|
||||
self._4dguns_rotated = true
|
||||
else
|
||||
self.object:set_properties({
|
||||
automatic_rotate = (not item_def.realistic) and math.pi * 0.5 * 0.2 / item_def.visual_size,
|
||||
automatic_rotate = math.pi * 0.5 * 0.2 / item_def.visual_size,
|
||||
})
|
||||
local rot = self.object:get_rotation()
|
||||
self.object:set_rotation({y=rot.y, x=0, z=0})
|
||||
self._rotated = true
|
||||
self._4dguns_rotated = true
|
||||
end
|
||||
end
|
||||
if not item_def then
|
||||
self:_respawn()
|
||||
end
|
||||
else
|
||||
if self._rotated then
|
||||
if self._4dguns_rotated then
|
||||
self.object:set_properties({
|
||||
automatic_rotate = 0,
|
||||
})
|
||||
self._rotated = false
|
||||
self._4dguns_rotated = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -266,7 +266,7 @@ end
|
||||
--for the following function only:
|
||||
--for license see the link on the next line (direct permission was granted).
|
||||
--https://github.com/3dreamengine/3DreamEngine
|
||||
function Guns4d.rltv_point_to_hud(pos, fov, aspect)
|
||||
function Guns4d.math.rltv_point_to_hud(pos, fov, aspect)
|
||||
local n = .1 --near
|
||||
local f = 1000 --far
|
||||
local scale = math.tan(fov * math.pi / 360)
|
||||
@ -287,7 +287,7 @@ end
|
||||
|
||||
--Code: Elkien3 (CC BY-SA 3.0)
|
||||
--https://github.com/Elkien3/spriteguns/blob/1c632fe12c35c840d6c0b8307c76d4dfa44d1bd7/init.lua#L76
|
||||
function Guns4d.nearest_point_on_line(lineStart, lineEnd, pnt)
|
||||
function Guns4d.math.nearest_point_on_line(lineStart, lineEnd, pnt)
|
||||
local line = vector.subtract(lineEnd, lineStart)
|
||||
local len = vector.length(line)
|
||||
line = vector.normalize(line)
|
||||
@ -297,3 +297,33 @@ function Guns4d.nearest_point_on_line(lineStart, lineEnd, pnt)
|
||||
d = Guns4d.math.clamp(d, 0, len);
|
||||
return vector.add(lineStart, vector.multiply(line, d))
|
||||
end
|
||||
|
||||
function Guns4d.math.rand_box_muller(deviation)
|
||||
local tau = math.pi*2
|
||||
--our first value cant be 0
|
||||
math.randomseed(math.random())
|
||||
local r1 = 0
|
||||
while r1 == 0 do r1=math.random() end
|
||||
local r2=math.random()
|
||||
print(r1, r2)
|
||||
|
||||
local a = deviation * math.sqrt(-2.0*math.log(r1))
|
||||
return a * math.cos(tau * r1), a * math.sin(tau * r2);
|
||||
end
|
||||
local e = 2.7182818284590452353602874713527 --I don't know how to find it otherwise...
|
||||
--deviation just changes the distribution, range is the maximum spread
|
||||
function Guns4d.math.angular_normal_distribution(deviation)
|
||||
local x=math.random()
|
||||
--print(x)
|
||||
--positive only normal distribution
|
||||
local a = (1/(deviation*math.sqrt(2*math.pi)))
|
||||
local exp = (-.5*(x/deviation)^2)
|
||||
local exp_x_1 = (-.5*(1/deviation)^2) --exp value where x=1
|
||||
local y=( (a*e^exp) - (a*e^exp_x_1) )/( a - (a*e^exp_x_1) ) --subtraction is to bring the value of x=1 to 0 on the curve and the division is to keep it normalized to an output of one
|
||||
print(y)
|
||||
local theta = math.random()*math.pi*2
|
||||
return y*math.cos(theta), y*math.sin(theta)
|
||||
end
|
||||
function Guns4d.math.round(n)
|
||||
return (n-math.floor(n)<.5 and math.floor(n)) or math.ceil(n)
|
||||
end
|
2
mod.conf
2
mod.conf
@ -3,4 +3,4 @@ title = guns4d
|
||||
description = Adds a library for 3d guns
|
||||
author = FatalError42O
|
||||
depends = mtul_b3d, mtul_cpml, mtul_filesystem
|
||||
optional_depends = spriteguns
|
||||
optional_depends = spriteguns, sprint
|
@ -35,7 +35,9 @@ local sqrt = math.sqrt
|
||||
-- @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 has_speed_of_sound = true
|
||||
-- @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".
|
||||
-- @table guns4d_soundspec
|
||||
|
||||
local function handle_min_max(tbl)
|
||||
@ -84,12 +86,11 @@ function Guns4d.play_sounds(soundspecs_list)
|
||||
end
|
||||
local handle = #sound_handles+1 --determine the sound handle before playing
|
||||
sound_handles[handle] = {}
|
||||
local handle_object = sound_handles[handle]
|
||||
--local handle_object = sound_handles[handle]
|
||||
for arg, soundspec in pairs(soundspecs_list) do
|
||||
if soundspec.to_player == "from_player" then soundspec.to_player = soundspec.player:get_player_name() end --setter of sound may not have access to this info, so add a method to use it.
|
||||
assert(not (soundspec.to_player and soundspec.min_distance), "in argument '"..tostring(arg).."' `min_distance` and `to_player` are incompatible parameters.")
|
||||
local sound = soundspec.sound
|
||||
local outval
|
||||
for i, v in pairs(soundspec) do
|
||||
if type(v) == "table" and v.min then
|
||||
soundspec[i]=handle_min_max(v)
|
||||
@ -102,28 +103,33 @@ function Guns4d.play_sounds(soundspecs_list)
|
||||
if not mtul.paths.media_paths[(sound or "[NIL]")..".ogg"] then
|
||||
minetest.log("error", "no sound by the name `"..mtul.paths.media_paths[(sound or "[NIL]")..".ogg"].."`")
|
||||
end
|
||||
local exclude_player_ref = soundspec.exclude_player
|
||||
if type(soundspec.exclude_player)=="string" then
|
||||
exclude_player_ref = minetest.get_player_by_name(soundspec.exclude_player)
|
||||
elseif soundspec.exclude_player then
|
||||
exclude_player_ref = soundspec.exclude_player
|
||||
soundspec.exclude_player = exclude_player_ref:get_player_name()
|
||||
end
|
||||
--print(dump(soundspecs_list), i)
|
||||
if soundspec.to_player then soundspec.pos = nil end
|
||||
if soundspec.min_hear_distance then
|
||||
local exclude_player_ref
|
||||
if soundspec.exclude_player then
|
||||
exclude_player_ref = minetest.get_player_by_name(soundspec.exclude_player)
|
||||
end
|
||||
--play sound for all players outside min hear distance
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
soundspec.sound = nil
|
||||
local pos = player:get_pos()
|
||||
local dist = sqrt( sqrt((pos.x-soundspec.pos.x)^2+(pos.y-soundspec.pos.y)^2)^2 + (pos.z-soundspec.pos.z)^2)
|
||||
if (dist > soundspec.min_hear_distance) and (player~=exclude_player_ref) then
|
||||
soundspec.exclude_player = nil --not needed anyway because we can just not play it for this player.
|
||||
soundspec.to_player = player:get_player_name()
|
||||
play_sound(sound, soundspec, handle, arg)
|
||||
end
|
||||
end
|
||||
else
|
||||
--print(dump(soundspec))
|
||||
--play sound for all players outside min hear distance
|
||||
local original_gain = soundspec.gain or 1
|
||||
local attenuation_rate = soundspec.attenuation_rate or Guns4d.config.default_audio_attenuation_rate
|
||||
local player_list = ((not soundspec.to_player) and minetest.get_connected_players()) or {minetest.get_player_by_name(soundspec.to_player)}
|
||||
for _, player in pairs(player_list) do
|
||||
soundspec.sound = nil
|
||||
play_sound(sound, soundspec, handle, arg)
|
||||
local pos = player:get_pos()
|
||||
local dist = 0
|
||||
if soundspec.pos then
|
||||
dist = sqrt( sqrt((pos.x-(soundspec.pos.x))^2+(pos.y-soundspec.pos.y)^2)^2 + (pos.z-soundspec.pos.z)^2)
|
||||
end
|
||||
if ((not soundspec.max_hear_distance) or (dist <= soundspec.max_hear_distance)) and ((not soundspec.min_hear_distance) or (dist > soundspec.min_hear_distance)) and (player~=exclude_player_ref) then
|
||||
print(player:get_player_name(), dist, sound, (dist-(soundspec.min_hear_distance or 0))*attenuation_rate, soundspec.min_hear_distance)
|
||||
soundspec.exclude_player = nil --not needed anyway because we can just not play it for this player.
|
||||
soundspec.to_player = player:get_player_name()
|
||||
soundspec.gain = original_gain/(Guns4d.math.clamp((dist-(soundspec.min_hear_distance or 0))*attenuation_rate, 1, math.huge)^2) --so i found out the hard way that it doesn't fucking reduce volume by distance if there's a to_player. Kind of pisses me off.
|
||||
play_sound(sound, soundspec, handle, arg)
|
||||
end
|
||||
end
|
||||
end
|
||||
return handle
|
||||
|
@ -75,7 +75,7 @@ function Guns4d.effects.muzzle_flash(self)
|
||||
}
|
||||
})
|
||||
end
|
||||
function Guns4d.effects.spawn_bullet_hole_particle(pos, size, texture)
|
||||
--[[function Guns4d.effects.spawn_bullet_hole_particle(pos, size, texture)
|
||||
--modern syntax isn't accepted by add particle to my knowledge, or it's not documented.
|
||||
--so I have to use a particle spawner
|
||||
minetest.add_particlespawner({
|
||||
@ -155,4 +155,4 @@ minetest.register_entity("guns4d:bullet_hole", {
|
||||
self.object:set_properties(properties)
|
||||
end
|
||||
end
|
||||
})
|
||||
})]]
|
Loading…
x
Reference in New Issue
Block a user