completely reworked sound

This commit is contained in:
FatalErr42O 2024-05-16 19:20:57 -07:00
parent 8212981d2d
commit c2da4ad0cb
16 changed files with 515 additions and 179 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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 = {}

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
})
})]]