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 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 for i, v in pairs(def.accepted_bullets) do
--TODO: make an actual error/minetest.log --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 def.accepted_bullets_set[v] = true
end end
Guns4d.ammo.registered_magazines[def.itemstring] = def Guns4d.ammo.registered_magazines[def.itemstring] = def

View File

@ -182,14 +182,36 @@ function Ammo_handler:load_magazine()
return return
end end
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 inv = self.inventory
local gun = self.gun local gun = self.gun
for i, v in pairs(inv:get_list("main")) do 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 return true
end 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 return true
end end
end end

View File

@ -1,62 +1,153 @@
local player_positions = {} local player_positions = {}
minetest.register_globalstep(function(dt) minetest.register_globalstep(function(dt)
for i, v in pairs(player_positions) do
end) player_positions[i] = nil
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)
end 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() function Bullet_hole:render()
if self.old_timer then assert(self.instance)
--acount for the time lost. self.rendered = true
self.timer = self.old_timer-(self.unrendered_exptime-self.timer)
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
end end
function Bullet_hole:unrender() function Bullet_hole:unrender()
self.old_timer = self.timer assert(self.instance)
self.timer = self.unrendered_exptime self.rendered = false
minetest.add_particlespawner({
pos = self.pos, local normal = vector.rotate(vector.new(0,0,1), self.rotation)
amount = 1, local time_left = self.exp_time/self.unrendered_expire_speed
time=0, local number_of_particles = 2
exptime = self.unrendered_exptime, 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 = { texture = {
name = 'bullet_hole.png', name = self.texture,
alpha_tween = {1,0} scale = self.size*10
} }
}) })
if self.entity:get_pos() then if self.entity:get_pos() then
self.entity:remove() self.entity:remove()
end end
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 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 end
minetest.register_entity("guns4d:bullet_hole", { minetest.register_entity("guns4d:bullet_hole", {
initial_properties = { initial_properties = {
visual = "cube", visual = "cube",
visual_size = {x=.15, y=.15, z=0}, visual_size = {x=.1, y=.1, z=0},
pointable = false, pointable = false,
static_save = false, static_save = false,
use_texture_alpha = true, use_texture_alpha = true,
textures = {"blank.png", "blank.png", "blank.png", "blank.png", "bullet_hole.png", "blank.png"} 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 if TICK % 50 then
local class_inst = self.class_Inst local lua_instance = self.lua_instance
if class_inst.timer < 30 then 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() 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) self.object:set_properties(properties)
end end
end end

View File

@ -3,8 +3,7 @@ local ray = {
state = "free", state = "free",
--pos = pos, --pos = pos,
last_node = "", last_node = "",
hole_entity = "guns4d:bullet_hole", bullet_hole_class = Guns4d.bullet_hole,
normal = vector.new(),
--last_dir --last_dir
--exit_direction = dir, --exit_direction = dir,
--range_left = def.bullet.range, --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 pass_sound_mixing_factor = Guns4d.config.default_pass_sound_mixing_factor, --determines the ratio to use based on energy
damage = 0, damage = 0,
energy = 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, ITERATION_DISTANCE = Guns4d.config.default_penetration_iteration_distance,
} }
@ -63,7 +66,7 @@ function ray:find_transverse_edge()
return pointed.intersection_point, pointed.intersection_normal return pointed.intersection_point, pointed.intersection_normal
end end
end end
function ray:_cast() function ray:cast()
assert(self.instance, "attempt to call obj method on a class") 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. 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_pos = self.pos+(self.dir*self.range)
end end
--do the main raycast. We don't account for mmRHA dropoff here. --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 cast = minetest.raycast(self.pos, end_pos, true, true)
local edge_length local edge_length
if edge then if edge then
@ -142,9 +145,9 @@ function ray:_cast()
return pointed_node, pointed_object, next_state, end_pos, end_normal, continue return pointed_node, pointed_object, next_state, end_pos, end_normal, continue
end end
--the main function. --the main function.
function ray:_iterate(initialized) function ray:iterate()
assert(self.instance, "attempt to call obj method on a class") 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) local distance = vector.distance(self.pos, end_pos)
if self.state == "free" then if self.state == "free" then
@ -163,7 +166,7 @@ function ray:_iterate(initialized)
--calc penetration loss from traveling through the block --calc penetration loss from traveling through the block
local penetration_loss = distance*Guns4d.node_properties[self.last_node_name].mmRHA 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. --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 end
--set values for next iteration. --set values for next iteration.
self.range = self.range-distance 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. 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 if continue and self.range > 0 and self.energy > 0 then
self:_iterate(true) self:iterate()
end end
--[[if not initialized then --[[if not initialized then
for i, v in pairs(self.history) do 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. 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) --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 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 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 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 end
function ray:bullet_hole(pos, normal) function ray:bullet_hole(pos, normal)
assert(self.instance, "attempt to call obj method on a class") 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 for pname, player in pairs(minetest.get_connected_players()) do
if vector.distance(player:get_pos(), pos) < 50 then if vector.distance(player:get_pos(), pos) < 50 then
nearby_players = true; break nearby_players = true; break
end end
end end]]
--if it's close enough to any players, then add it --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. --this entity will keep track of itself.
local ent = minetest.add_entity(pos+(normal*(.0001+math.random()/1000)), self.hole_entity) local ent = minetest.add_entity(pos+(normal*(.0001+math.random()/1000)), self.hole_entity)
ent:set_rotation(vector.dir_to_rotation(normal)) ent:set_rotation(vector.dir_to_rotation(normal))
@ -280,18 +283,23 @@ function ray:bullet_hole(pos, normal)
lua_ent.block_pos = pos lua_ent.block_pos = pos
else else
Guns4d.effects.spawn_bullet_hole_particle(pos, self.hole_scale, '(bullet_hole_1.png^(bullet_hole_2.png^[opacity:129))') 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 end
function ray:play_bullet_pass_sounds() function ray:play_bullet_pass_sounds()
--iteration done, damage applied, find players to apply bullet whizz to --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 = {} local played_for = {}
for i = #self.history, 1, -1 do for i = #self.history, 1, -1 do
local v = self.history[i] local v = self.history[i]
for _, player in pairs(minetest.get_connected_players()) do for _, player in pairs(minetest.get_connected_players()) do
if (player~=self.player) and not played_for[player] then 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 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 if vector.distance(nearest, pos) < self.pass_sound_max_distance then
played_for[player] = true played_for[player] = true
if self.pass_sounds[1] then if self.pass_sounds[1] then
@ -301,7 +309,7 @@ function ray:play_bullet_pass_sounds()
else else
--interpolate to find the energy of the shot to determine supersonic or not. --interpolate to find the energy of the shot to determine supersonic or not.
local v1 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 v2 = v.energy
local ip_r = vector.distance(start_pos, nearest)/vector.distance(start_pos, pos) 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 start_pos = v.pos
end end
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) function ray.construct(def)
if def.instance then if def.instance then
--these asserts aren't necessary, probably drags down performance a tiny bit. --these asserts aren't necessary, probably drags down performance a tiny bit.
@ -379,15 +413,40 @@ function ray.construct(def)
end end
def.energy_sharp_ratio = (def.energy-def.blunt_penetration)/def.energy 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. --blunt pen is in the same units (1 Joule/Area^3 = 1 Pa), so we use it to make the ratio by subtraction.
local init_def = def.init_def
def.dir = vector.new(def.dir) for i=1,def.pellets do
def.pos = vector.new(def.pos) local x, y = Guns4d.math.angular_normal_distribution(def.spread_deviation)
def.history = {} --x, y = (math.random()-.5)*2, (math.random()-.5)*2
def.init_pos = vector.new(def.pos) --has to be cloned before iteration local dir = def.gun:get_dir(false, x*def.spread, y*def.spread)
def:_iterate() if def.wall_penetration then
def:play_bullet_pass_sounds() 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
end end
Guns4d.bullet_ray = Instantiatable_class:inherit(ray) Guns4d.bullet_ray = Instantiatable_class:inherit(ray)

View File

@ -12,6 +12,9 @@ Guns4d.control_handler = {
loop = false, loop = false,
func=function(active, interrupted, data, busy_controls) 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, ads = false,
@ -114,6 +117,12 @@ function controls:on_use(itemstack, pointed_thing)
actions.on_use(itemstack, self.handler, pointed_thing) actions.on_use(itemstack, self.handler, pointed_thing)
end end
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) function controls:on_secondary_use(itemstack, pointed_thing)
assert(self.instance, "attempt to call object method on a class") assert(self.instance, "attempt to call object method on a class")
local actions = self:get_actions() local actions = self:get_actions()

View File

@ -30,7 +30,7 @@ end
local function render_length(rotation, fov) 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}) 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}) 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) return math.sqrt(out.x^2+out.y^2)
end end
function Dynamic_crosshair:update(dt) function Dynamic_crosshair:update(dt)
@ -85,7 +85,7 @@ function Dynamic_crosshair:update(dt)
--now figure out what frame will be our correct spread --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 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 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" charging = { --how the gun "cocks"
require_draw_on_swap = true, 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. 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 --sound = soundspec
}, },
reload = { --used by defualt controls. Still provides usefulness elsewhere. reload = { --used by defualt controls. Still provides usefulness elsewhere.
@ -135,12 +137,14 @@ local gun_default = {
}, },
ammo = { --used by ammo_handler ammo = { --used by ammo_handler
magazine_only = false, magazine_only = false,
--capacity = 0, --this is only needed if magazine_only = false
accepted_bullets = {}, accepted_bullets = {},
accepted_magazines = {}, accepted_magazines = {},
initial_mag = "empty" initial_mag = "empty"
}, },
visuals = { visuals = {
--mesh --textures = {},
--mesh="string.b3d",
backface_culling = true, backface_culling = true,
root = "gun", root = "gun",
magazine = "magazine", magazine = "magazine",
@ -252,8 +256,6 @@ local gun_default = {
consts = { consts = {
HIP_PLAYER_GUN_ROT_RATIO = .75, HIP_PLAYER_GUN_ROT_RATIO = .75,
AIM_OUT_AIM_IN_SPEED_RATIO = 2.5, 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. 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)]] 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, WAG_CYCLE_SPEED = 1.6,
@ -266,7 +268,10 @@ local gun_default = {
HAS_WAG = true, HAS_WAG = true,
HAS_GUN_AXIAL_OFFSETS = true, HAS_GUN_AXIAL_OFFSETS = true,
ANIMATIONS_OFFSET_AIM = false, 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. --[[animation_data = { --where animations data is stored.
anim_runtime = 0, anim_runtime = 0,
@ -295,17 +300,15 @@ end
function gun_default:draw() function gun_default:draw()
assert(self.instance, "attempt to call object method on a class") assert(self.instance, "attempt to call object method on a class")
local props = self.properties local props = self.properties
if props.visuals.animations.draw then if props.visuals.animations[props.charging.draw_animation] then
self:set_animation(props.visuals.animations.draw, props.charging.default_draw_time) self:set_animation(props.visuals.animations[props.charging.draw_animation], props.charging.draw_time)
end end
if props.sounds.draw then if props.sounds[props.charging.draw_sound] then
local sounds = Guns4d.table.deep_copy(props.sounds.draw) local sounds = Guns4d.table.deep_copy(props.sounds[props.charging.draw_sound])
sounds.player = self.player self:play_sounds(sounds)
sounds.pos = self.pos
Guns4d.play_sounds(sounds)
end end
self.ammo_handler:chamber_round() self.ammo_handler:chamber_round()
self.rechamber_time = props.charging.default_draw_time self.rechamber_time = props.charging.draw_time
end end
--update gun, the main function. --update gun, the main function.
function gun_default:update(dt) function gun_default:update(dt)
@ -442,7 +445,7 @@ function gun_default:attempt_fire()
player = self.player, player = self.player,
--we don't want it to be doing fuckshit and letting players shoot through walls. --we don't want it to be doing fuckshit and letting players shoot through walls.
pos = pos-((self.handler.control_handler.ads and dir*self.properties.ads.offset.z) or dir*self.properties.hip.offset.z), pos = pos-((self.handler.control_handler.ads and dir*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 gun = self
}) })
Guns4d.bullet_ray:new(bullet_def) Guns4d.bullet_ray:new(bullet_def)
@ -455,7 +458,7 @@ function gun_default:attempt_fire()
--print(dump(self.properties.sounds.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. 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 fire_sound.pos = self.pos
Guns4d.play_sounds(fire_sound) self:play_sounds(fire_sound)
self.rechamber_time = 60/self.properties.firerateRPM self.rechamber_time = 60/self.properties.firerateRPM
return true return true
@ -516,37 +519,72 @@ function gun_default:get_player_axial_dir(rltv)
end, hud)]] end, hud)]]
return dir return dir
end 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") assert(self.instance, "attempt to call object method on a class")
local rotation = self.total_offset_rotation local rotation = self.total_offset_rotation
local handler = self.handler local handler = self.handler
--rotate x and then y. --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})) --old code. I used a site (symbolab.com) to precalculate the rotation matrices to save on performance since spread has to run this.
dir = Vec.rotate(dir, {y=((rotation.gun_axial.y+rotation.player_axial.y)*math.pi/180), x=0, z=0}) --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})
if not rltv then --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 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. --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 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 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 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 return dir
end 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. --broken! doesn't properly reflect values.
function gun_default:get_pos(added_pos, relative, debug) function gun_default:get_pos(added_pos, relative, debug)
assert(self.instance, "attempt to call object method on a class") 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) 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
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) function gun_default:update_breathing(dt)
assert(self.instance)
local breathing_info = {pause=1.4, rate=4.2} 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- --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. --"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 dir = dir + (self.gun.properties.ads.offset+vector.new(self.gun.properties.ads.horizontal_offset,0,0))*0
end end
local fov = self.player:get_fov() local fov = self.player:get_fov()
local real_aim = Guns4d.rltv_point_to_hud(dir, fov, ratio) local real_aim = Guns4d.math.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 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 real_aim.x = real_aim.x+anim_aim.x; real_aim.y = real_aim.y+anim_aim.y
--print(dump(self.gun.animation_rotation)) --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 --so custom scopes can do their thing without doing more calcs
self.hud_projection_real = real_aim self.hud_projection_real = real_aim
self.hud_projection_paxial = paxial_aim self.hud_projection_paxial = paxial_aim

View File

@ -198,46 +198,60 @@ local function initialize_b3d_animation_data(self, props)
end]] end]]
--print() --print()
end 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.") assert(self.itemstring, "no itemstring provided. Cannot create a gun without an associated itemstring.")
local item_def = minetest.registered_items[self.itemstring] local item_def = minetest.registered_items[self.itemstring]
assert(rawget(self, "name"), "no name provided in new class") assert(rawget(self, "name"), "no name provided in new class")
assert(rawget(self, "itemstring"), "no itemstring 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.") assert(item_def, self.itemstring.." : item is not registered.")
--override methods so control handler can do it's job --override methods so control handler can do it's job
local old_on_use = item_def.on_use local old_on_use = item_def.on_use
local old_on_s_use = item_def.on_secondary_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 self.properties.inventory_image = item_def.inventory_image
--override the item to hook in controls. (on_drop needed) --override the item to hook in controls. (on_drop needed)
minetest.override_item(self.itemstring, { minetest.override_item(self.itemstring, {
on_use = function(itemstack, user, pointed_thing) 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) 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, end,
on_secondary_use = function(itemstack, user, pointed_thing) 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) 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 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 end
local function create_visual_entity(self,props) local function create_visual_entity(def, props)
minetest.register_entity(self.name.."_visual", { minetest.register_entity(def.name.."_visual", {
initial_properties = { initial_properties = {
visual = "mesh", visual = "mesh",
mesh = props.visuals.mesh, mesh = props.visuals.mesh,
textures = props.textures, textures = props.visuals.textures,
glow = 0, glow = 0,
pointable = false, pointable = false,
static_save = false, static_save = false,
backface_culling = props.visuals.backface_culling backface_culling = props.visuals.backface_culling
}, },
on_step = function(self, dtime) on_step = --[[def.entity_function or]] function(self)
local obj = self.object local obj = self.object
if not self.parent_player then obj:remove() return end if not self.parent_player then obj:remove() return end
local player = self.parent_player local player = self.parent_player
@ -258,11 +272,11 @@ local function create_visual_entity(self,props)
end end
if handler.control_handler.ads then if handler.control_handler.ads then
local normal_pos = (props.ads.offset)*10 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 else
local normal_pos = vector.new(props.hip.offset)*10 local normal_pos = vector.new(props.hip.offset)*10
-- vector.multiply({x=normal_pos.x, y=normal_pos.z, z=-normal_pos.y}, 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
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 it's not a template, then create an item, override some props
if self.name ~= "__template" then if self.name ~= "__template" then
complete_item(self, props) reregister_item(self, props)
end end
--create sets. This may need to be put in instances of modifications can change accepted ammos --create sets. This may need to be put in instances of modifications can change accepted ammos
self.accepted_bullets = {} self.accepted_bullets = {}

View File

@ -67,7 +67,7 @@ end
local reload_actions = {} local reload_actions = {}
function Guns4d.default_controls.register_reloading_state_type(name, def) function Guns4d.default_controls.register_reloading_state_type(name, def)
assert(type(def)=="table", "improper definition type") 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 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 reload_actions[name] = def
end end
@ -92,21 +92,21 @@ reg_mstate("unload_mag", {
reg_mstate("store", { reg_mstate("store", {
on_completion = function(gun, ammo_handler, next_state) 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 --needs to happen before so we don't detect the ammo we just unloaded
if not ammo_handler:inventory_has_ammo() then if not ammo_handler:inventory_has_ammo() then
pause=true pause=true
end end]]
if gun.properties.ammo.magazine_only and (ammo_handler.ammo.loaded_mag ~= "empty") then if gun.properties.ammo.magazine_only and (ammo_handler.ammo.loaded_mag ~= "empty") then
ammo_handler:unload_magazine() ammo_handler:unload_magazine()
else else
ammo_handler:unload_all() ammo_handler:unload_all()
end end
--if there's no ammo make hold so you don't reload the same ammo you just unloaded. --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 return
end end
return true return true]]
end, end,
validation_check = function(gun, ammo_handler, next_state) validation_check = function(gun, ammo_handler, next_state)
if gun.properties.ammo.magazine_only and (ammo_handler.ammo.loaded_mag == "empty") then if gun.properties.ammo.magazine_only and (ammo_handler.ammo.loaded_mag == "empty") then
@ -150,7 +150,19 @@ reg_mstate("load", {
return true return true
end 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", { reg_mstate("charge", {
on_completion = function(gun, ammo_handler) on_completion = function(gun, ammo_handler)
ammo_handler:chamber_round() ammo_handler:chamber_round()
@ -159,8 +171,9 @@ reg_mstate("charge", {
validation_check = function(gun, ammo_handler, next_state) validation_check = function(gun, ammo_handler, next_state)
if (ammo_handler.ammo.next_bullet ~= "empty") or (ammo_handler.ammo.total_bullets == 0) then if (ammo_handler.ammo.next_bullet ~= "empty") or (ammo_handler.ammo.total_bullets == 0) then
return false return false
else
return true
end end
return true
end end
}) })
Guns4d.default_controls.reload = { Guns4d.default_controls.reload = {
@ -259,11 +272,10 @@ Guns4d.default_controls.reload = {
if type(next_state.sounds) == "table" then if type(next_state.sounds) == "table" then
sounds = Guns4d.table.deep_copy(props.reload[next_state_index].sounds) sounds = Guns4d.table.deep_copy(props.reload[next_state_index].sounds)
elseif type(next_state.sounds) == "string" then 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 end
sounds.pos = gun.pos sounds.pos = gun.pos
sounds.max_hear_distance = sounds.max_hear_distance or gun.consts.DEFAULT_MAX_HEAR_DISTANCE data.played_sounds = {gun:play_sounds(sounds)}
data.played_sounds = Guns4d.play_sounds(sounds)
end end
--print(dump(next_state_index)) --print(dump(next_state_index))
--end --end

View File

@ -11,7 +11,7 @@ Guns4d.config = {
show_gun_inv_ammo_count = true, show_gun_inv_ammo_count = true,
control_hybrid_toggle_threshold = .3, control_hybrid_toggle_threshold = .3,
control_held_toggle_threshold = 0, control_held_toggle_threshold = 0,
empty_symbol = "0e", empty_symbol = "E",
default_damage_group = "fleshy", default_damage_group = "fleshy",
infinite_ammo_priv = "guns4d_infinite_ammo", infinite_ammo_priv = "guns4d_infinite_ammo",
interpret_initial_wear_as_ammo = false, interpret_initial_wear_as_ammo = false,
@ -23,15 +23,19 @@ Guns4d.config = {
headshot_damage_factor = 1.75, headshot_damage_factor = 1.75,
enable_touchscreen_command_name = "guns4d_enable_touchmode", 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 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, mix_supersonic_and_subsonic_sounds = true,
default_pass_sound_mixing_factor = 10, default_pass_sound_mixing_factor = 10,
third_person_gain_multiplier = 1/3,
default_penetration_iteration_distance = .25, default_penetration_iteration_distance = .25,
maximum_bullet_holes = 20,
--enable_assert = false,
realistic_items = true
--`["official_content.replace_ads_with_bloom"] = false, --`["official_content.replace_ads_with_bloom"] = false,
--`["official_content.uses_magazines"] = true --`["official_content.uses_magazines"] = true
} }
local path = minetest.get_modpath("guns4d") local path = minetest.get_modpath("guns4d")
print("file read?")
local conf = Settings(path.."/guns4d_settings.conf"):to_table() or {} 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 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 for i, v in pairs(Guns4d.config) do
@ -44,6 +48,7 @@ for i, v in pairs(Guns4d.config) do
end end
end end
dofile(path.."/infinite_ammo.lua") dofile(path.."/infinite_ammo.lua")
dofile(path.."/misc_helpers.lua") dofile(path.."/misc_helpers.lua")
dofile(path.."/item_entities.lua") dofile(path.."/item_entities.lua")
@ -55,6 +60,7 @@ dofile(path.."/block_values.lua")
dofile(path.."/ammo_api.lua") dofile(path.."/ammo_api.lua")
path = path .. "/classes" path = path .. "/classes"
dofile(path.."/Instantiatable_class.lua") dofile(path.."/Instantiatable_class.lua")
dofile(path.."/Bullet_hole.lua")
dofile(path.."/Bullet_ray.lua") dofile(path.."/Bullet_ray.lua")
dofile(path.."/Control_handler.lua") dofile(path.."/Control_handler.lua")
dofile(path.."/Ammo_handler.lua") dofile(path.."/Ammo_handler.lua")

View File

@ -33,7 +33,11 @@ local defaults = {
--light_source = 0, --light_source = 0,
collisionbox_size = 2, collisionbox_size = 2,
visual_size = 1, 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 --- replaces the item entity of the provided item with a 3d entity based on the definition
-- @param itemstring -- @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(minetest.registered_items[itemstring], "item: `"..tostring(itemstring).."` not registered by minetest")
assert(type(def)=="table", "definition is not a table") assert(type(def)=="table", "definition is not a table")
def = Guns4d.table.fill(defaults, def) 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 Guns4d.registered_items[itemstring] = def
end end
@ -63,60 +63,80 @@ def.set_item = function(self, item)
end end
local item_def = Guns4d.registered_items[stack:get_name()] local item_def = Guns4d.registered_items[stack:get_name()]
local cbox --[[local a = item_def.collisionbox_size
local sbox local o = item_def.collisionbox_offset
local a = item_def.collisionbox_size
local b = item_def.selectionbox local b = item_def.selectionbox
if item_def.realistic == true then 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. 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/20, 0, -b.z/20, b.x/20, b.y/10, b.z/20, rotate=true} 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 else
cbox = {-a/20, -a/20, -a/20, a/20, a/20, a/20} 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/20, -b.y/20, -b.z/20, b.x/20, b.y/20, b.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 end]]
local cbox = item_def.collisionbox
local sbox = item_def.selectionbox
self.object:set_properties({ self.object:set_properties({
is_visible = true, is_visible = true,
visual = "mesh", visual = "mesh",
mesh = item_def.mesh, mesh = item_def.mesh,
textures = item_def.textures, textures = item_def.textures,
collisionbox = cbox, 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), 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}, 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(), infotext = stack:get_description(),
}) })
self._collisionbox = cbox --self._collisionbox = cbox
end end
local old = def.on_step 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, ...) def.on_step = function(self, dt, mr, ...)
old(self, dt, mr, ...) old(self, dt, mr, ...)
--icky nesting. --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 if mr and mr.touching_ground then
local item_def = Guns4d.registered_items[ItemStack(self.itemstring):get_name()] item_def = item_def or Guns4d.registered_items[ItemStack(self.itemstring):get_name()]
if item_def and not self._rotated then if item_def and not self._4dguns_rotated then
if item_def.realistic then if item_def.realistic then
self.object:set_properties({ 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() local rot = self.object:get_rotation()
self.object:set_rotation({y=rot.y, x=rot.x+(math.pi/2), z=0}) self.object:set_rotation({y=rot.y, x=rot.x, z=math.pi*.5})
self._rotated = true self._4dguns_rotated = true
else else
self.object:set_properties({ 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() local rot = self.object:get_rotation()
self.object:set_rotation({y=rot.y, x=0, z=0}) self.object:set_rotation({y=rot.y, x=0, z=0})
self._rotated = true self._4dguns_rotated = true
end end
end end
if not item_def then
self:_respawn()
end
else else
if self._rotated then if self._4dguns_rotated then
self.object:set_properties({ self.object:set_properties({
automatic_rotate = 0, automatic_rotate = 0,
}) })
self._rotated = false self._4dguns_rotated = false
end end
end end
end end

View File

@ -266,7 +266,7 @@ end
--for the following function only: --for the following function only:
--for license see the link on the next line (direct permission was granted). --for license see the link on the next line (direct permission was granted).
--https://github.com/3dreamengine/3DreamEngine --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 n = .1 --near
local f = 1000 --far local f = 1000 --far
local scale = math.tan(fov * math.pi / 360) local scale = math.tan(fov * math.pi / 360)
@ -287,7 +287,7 @@ end
--Code: Elkien3 (CC BY-SA 3.0) --Code: Elkien3 (CC BY-SA 3.0)
--https://github.com/Elkien3/spriteguns/blob/1c632fe12c35c840d6c0b8307c76d4dfa44d1bd7/init.lua#L76 --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 line = vector.subtract(lineEnd, lineStart)
local len = vector.length(line) local len = vector.length(line)
line = vector.normalize(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); d = Guns4d.math.clamp(d, 0, len);
return vector.add(lineStart, vector.multiply(line, d)) return vector.add(lineStart, vector.multiply(line, d))
end 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 description = Adds a library for 3d guns
author = FatalError42O author = FatalError42O
depends = mtul_b3d, mtul_cpml, mtul_filesystem 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 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 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 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 -- @table guns4d_soundspec
local function handle_min_max(tbl) local function handle_min_max(tbl)
@ -84,12 +86,11 @@ function Guns4d.play_sounds(soundspecs_list)
end end
local handle = #sound_handles+1 --determine the sound handle before playing local handle = #sound_handles+1 --determine the sound handle before playing
sound_handles[handle] = {} sound_handles[handle] = {}
local handle_object = sound_handles[handle] --local handle_object = sound_handles[handle]
for arg, soundspec in pairs(soundspecs_list) do 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. 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.") 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 sound = soundspec.sound
local outval
for i, v in pairs(soundspec) do for i, v in pairs(soundspec) do
if type(v) == "table" and v.min then if type(v) == "table" and v.min then
soundspec[i]=handle_min_max(v) 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 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"].."`") minetest.log("error", "no sound by the name `"..mtul.paths.media_paths[(sound or "[NIL]")..".ogg"].."`")
end 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) --print(dump(soundspecs_list), i)
if soundspec.to_player then soundspec.pos = nil end if soundspec.to_player then soundspec.pos = nil end
if soundspec.min_hear_distance then --play sound for all players outside min hear distance
local exclude_player_ref local original_gain = soundspec.gain or 1
if soundspec.exclude_player then local attenuation_rate = soundspec.attenuation_rate or Guns4d.config.default_audio_attenuation_rate
exclude_player_ref = minetest.get_player_by_name(soundspec.exclude_player) local player_list = ((not soundspec.to_player) and minetest.get_connected_players()) or {minetest.get_player_by_name(soundspec.to_player)}
end for _, player in pairs(player_list) do
--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))
soundspec.sound = nil 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
end end
return handle return handle

View File

@ -75,7 +75,7 @@ function Guns4d.effects.muzzle_flash(self)
} }
}) })
end 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. --modern syntax isn't accepted by add particle to my knowledge, or it's not documented.
--so I have to use a particle spawner --so I have to use a particle spawner
minetest.add_particlespawner({ minetest.add_particlespawner({
@ -155,4 +155,4 @@ minetest.register_entity("guns4d:bullet_hole", {
self.object:set_properties(properties) self.object:set_properties(properties)
end end
end end
}) })]]