fixed some issues with reloading. Fixed an issue where magazines broke all recipes in game except the most recently registered magazine's reload

This commit is contained in:
FatalErr42O 2024-02-29 21:18:37 -08:00
parent 8585180634
commit 6e74815ab7
20 changed files with 627 additions and 254 deletions

View File

@ -4,6 +4,7 @@
"vector",
"dump",
"player_api",
"ItemStack"
"ItemStack",
"mtul"
]
}

View File

@ -1,14 +1,58 @@
( ) guns4d_pack_1
( ) find better name
-guns4d-WMA (western military arms)
( ) finish guns
(~) m4
( ) add reload anim hand movement
(~) awm
(~) complete animations
( ) load, unload
(~) complete sound effects
( ) load, unload
( ) mp5
( ) Vector
( ) berreta (pistol)
( ) glock
( ) spas12
( ) guns4d_pack_2
( ) find better name
-guns4d-EMA (eastern military arms)
( ) ak74
( ) ak47?
( ) SVD
( ) PMM
( ) Saiga 12k
( ) Vitzyaz-SN
( ) guns4d_pack_3
( ) find better name
-guns4d-OP4 (opposing force's arms)
( ) make guns
( ) rugged ak47
( ) m1911
( ) RPG?
( ) Mossberg
( ) AR15
( ) Berreta m12 (smg)
(~) add audio
( ) have breathing act as a multiplier for sway
( ) fix water/fluids
( ) add random deviation
( ) randomness system
( ) patterns?
( ) bloom
(x) add audio
soundeffects system implemented!
(x) sfx system
(x) did signifficant work on it
(x) firing sound effects
( ) reload sound effects
( ) firemode sound effects
( ) dry fire sound
(~) reload sound effects
(x) everything else
( ) bolt-catch releasing click
(x) firemode sound effects
( ) firemode switching sound
(~) add config
(x) add a table for config storage, some settings
( ) integrate with minetest settings
@ -16,18 +60,32 @@
(x) add infinite ammo privelage and quick command
(x) privilege not directly tied to infinite ammo, fix without breaking performance?
(x) fix animation rotation offset not displaying the correct frame
-was a problem with MTUL, changes push :D
( ) add third idle animation for loaded with magazine but chamber empty
( ) use newly implemented bolt catch support
( ) add entity scopes (for holo sights etc)
( ) add attachments
( ) add other reloading types
(~) add other reloading types
(x) magazine
( ) fractional
( ) flat (magless)
( ) fractional clip
( ) fractional with clip
(~) item visuals
( ) make optional
(~) magazine
(x) unloads in correct position? (sort of, rotation is like impossible to do.)
( ) realistic
(x) arcade/default
( ) gun
( ) realistic
( ) arcade/default
(x) fix uncharge gun after incomplete reload, make special pull out animation only play for first charge
(x) make reloads "automatic" (press/hold z once, and again to cancel)
(x) add control system
(x) implement for reload (may not be needed if first step done correctly)
( ) (for 5.9) make infinite ammo priv to rely on on_grant and on_revoke callback for runtime changes (cannot be done as broken in 5.8)
( ) (5.9) POTENTIALLY make models use new PR that allows bone offsets to be disabled
-I'd probably have to modify models at loadtime to have an eye and hipfire bone? Probably easier then current system though.
( ) Fix HORRIBLE namespace violation in misc_helpers.lua. Also migrate features to MTUL libraries
(x) Fix HORRIBLE namespace violation in misc_helpers.lua. Also migrate features to MTUL libraries
this won't be fun.
documentation

View File

@ -11,16 +11,10 @@ Default_mag = {
craft_reload = true
}
Guns4d.ammo = {
default_empty_loaded_bullets = {
},
registered_bullets = {
},
registered_magazines = {
}
default_empty_loaded_bullets = {},
registered_bullets = {},
registered_magazines = {}
}
local max_wear = 65535
function Guns4d.ammo.on_hit_player(bullet, force_mmRHA)
end
function Guns4d.ammo.register_bullet(def)
@ -39,14 +33,9 @@ function Guns4d.ammo.update_mag(def, itemstack, meta)
meta = meta or itemstack:get_meta()
local bullets = minetest.deserialize(meta:get_string("guns4d_loaded_bullets"))
local count = 0
local current_bullet = "empty"
for i, v in pairs(bullets) do
current_bullet = i
count = count + v
end
local new_wear = max_wear-(max_wear*count/def.capacity)
--itemstack:set_wear(Guns4d.math.clamp(new_wear, 1, max_wear-1))
meta:set_int("guns4d_total_bullets", count)
if count > 0 then
meta:set_string("count_meta", tostring(count).."/"..def.capacity)
else
@ -54,7 +43,6 @@ function Guns4d.ammo.update_mag(def, itemstack, meta)
end
return itemstack
end
function Guns4d.ammo.register_magazine(def)
def = Guns4d.table.fill(Default_mag, def)
assert(def.accepted_bullets, "missing property def.accepted_bullets. Need specified bullets to allow for loading")
@ -68,6 +56,8 @@ function Guns4d.ammo.register_magazine(def)
Guns4d.ammo.registered_magazines[def.itemstring] = def
--register craft prediction
local old_on_use = minetest.registered_items[def.itemstring].on_use
--the actual item. This will be changed.
minetest.override_item(def.itemstring, {
on_use = function(itemstack, user, pointed_thing)
if old_on_use then
@ -85,6 +75,24 @@ function Guns4d.ammo.register_magazine(def)
end
end
})
--the magazine item entity
--print(dump(minetest.registered_entities))
--[[local ent_def = minetest.registered_entities["__builtin:item"..def.itemstring]
if def.model then
ent_def.visual = "mesh"
ent_def.mesh = def.model
ent_def.collision_box = {
-0.5, 0, -0.5,
0.5, 1, 0.5
}
ent_def.on_step = function(self, dt, moveresult)
if moveresult.touching_ground then
self.object:set_rotation()
end
end
end]]
--loading and unloading magazines
if def.craft_reload then
minetest.register_craft_predict(function(itemstack, player, old_craft_grid, craft_inv)
--initialize all mags
@ -95,11 +103,12 @@ function Guns4d.ammo.register_magazine(def)
Guns4d.ammo.initialize_mag_data(v)
end
end
if num_mags > 0 then
minetest.chat_send_all(num_mags)
if num_mags == 1 then
if itemstack:get_name()=="" then
for i, v in pairs(craft_inv:get_list("craft")) do
local name =v:get_name()
if name == def.itemstring then
local name = v:get_name()
if (name == def.itemstring) and (v:get_meta():get_string("guns4d_loaded_bullets")=="") then
craft_inv:set_stack("craft", i, Guns4d.ammo.initialize_mag_data(v))
end
if (name~=def.itemstring) and Guns4d.ammo.registered_magazines[name] then
@ -111,35 +120,41 @@ function Guns4d.ammo.register_magazine(def)
end
return def.itemstring
end
elseif num_mags > 1 then
return ""
end
end)
minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
if craft_inv:contains_item("craft", def.itemstring) and craft_inv:contains_item("craftpreview", def.itemstring) then
local mag_stack_index
local craft_list = craft_inv:get_list("craft")
--there's basically no way to cleanly avoid two iterations, annoyingly.
--check for bullets and mags.
local mag_stack_index
for i, v in pairs(craft_list) do
local name = v:get_name()
if (name~=def.itemstring) then
if Guns4d.ammo.registered_magazines[name] then
if Guns4d.ammo.registered_magazines[name] then
--check if there is a magazine of a different type or multiple mags, also get our mag index
if (name==def.itemstring) then
mag_stack_index = i
else
return
end
else
mag_stack_index = i
end
if not def.accepted_bullets_set[name] then
if (name ~= "") and (name~=def.itemstring) then
return
end
if (not def.accepted_bullets_set[name]) and (name ~= "") and (name~=def.itemstring) then
return
end
end
if not mag_stack_index then return end
local bullets_unfilled = def.capacity
local mag_stack = craft_inv:get_stack("craft", mag_stack_index)
--print(dump(mag_stack:get_name()))
--print(mag_stack_index)
local new_ammo_table = minetest.deserialize(mag_stack:get_meta():get_string("guns4d_loaded_bullets"))
for i, v in pairs(new_ammo_table) do
bullets_unfilled = bullets_unfilled - v
end
local new_stack = ItemStack(def.itemstring)
--find the bullets, and fill the new_ammo_table up to any items with counts adding up to bullets_unfilled
for i, v in pairs(craft_list) do
local name = v:get_name()
if def.accepted_bullets_set[name] then

View File

@ -40,6 +40,23 @@ function Ammo_handler:update_meta(bullets)
end
self.handler.player:set_wielded_item(self.gun.itemstack)
end
function Ammo_handler:get_magazine_bone_info()
local gun = self.gun
local handler = self.gun.handler
local node = mtul.b3d_nodes.get_node_by_name(gun.b3d_model, gun.properties.visuals.magazine, true)
--get trans of first frame
if not node then
minetest.log("error", "improperly set gun magazine bone name, could not properly calculate magazine transformation.")
return self.gun.pos, vector.new(), vector.new()
end
local pos1 = vector.new(mtul.b3d_nodes.get_node_global_position(nil, node, true, math.floor(gun.animation_data.current_frame)))
local pos2 = vector.new(mtul.b3d_nodes.get_node_global_position(nil, node, true, gun.animation_data.current_frame))
local vel = (pos2-pos1)*((gun.animation_data.current_frame-math.floor(gun.animation_data.current_frame))/gun.animation_data.fps)+self.gun.player:get_velocity()
local pos = self.gun:get_pos(pos2/10)+self.gun.handler:get_pos()
--[[so I tried to get the rotation before, and it actually turns out that was... insanely difficult? Why? I don't know, the rotation behavior was beyond unexpected, I tried implementing it with quats and
matrices and neither worked. I'm done, I've spent countless hours on this, and its fuckin stupid to spend a SECOND more on this pointless shit. It wouldnt even look that much better!]]
return pos, vel
end
--use a round, called when the gun is shot. Returns a bool indicating success.
function Ammo_handler:spend_round()
assert(self.instance, "attempt to call object method on a class")
@ -52,6 +69,9 @@ function Ammo_handler:spend_round()
self.ammo.loaded_bullets[bullet_spent] = self.ammo.loaded_bullets[bullet_spent]-1
if self.ammo.loaded_bullets[bullet_spent] == 0 then self.ammo.loaded_bullets[bullet_spent] = nil end
self.ammo.total_bullets = self.ammo.total_bullets - 1
if (self.ammo.total_bullets == 0) and (self.gun.properties.charging.bolt_charge_mode == "catch") then
self.gun.bolt_charged = true
end
end
--set the new current bullet
if next(self.ammo.loaded_bullets) then
@ -60,16 +80,35 @@ function Ammo_handler:spend_round()
else
self.ammo.next_bullet = "empty"
meta:set_string("guns4d_next_bullet", "empty")
if self.gun.properties.charging.bolt_charge_mode == "catch" then
self.gun.bolt_charged = true
end
end
self:update_meta()
return bullet_spent
end
end
function Ammo_handler:close_bolt()
self.ammo.next_bullet = Guns4d.math.weighted_randoms(self.ammo.loaded_bullets) or "empty"
function Ammo_handler:chamber_round()
self.ammo.next_bullet = Guns4d.math.weighted_randoms(self.ammo.loaded_bullets) or "empty"
end
function Ammo_handler:load_magazine(charge)
local function tally_ammo_from_meta(meta)
local string = meta:get_string("guns4d_loaded_bullets")
if string=="" then return 0 end
local count = 0
for i, v in pairs(minetest.deserialize(string)) do
count=count+v
end
return count
end
local function tally_ammo(ammo)
local total = 0
for i, v in pairs(ammo) do
total = total + v
end
return total
end
function Ammo_handler:load_magazine()
assert(self.instance, "attempt to call object method on a class")
local inv = self.inventory
local magstack_index
@ -82,17 +121,17 @@ function Ammo_handler:load_magazine(charge)
end
for i, v in pairs(inv:get_list("main")) do
if gun_accepts[v:get_name()] then
local meta = v:get_meta()
local mag_meta = v:get_meta()
--intiialize data if it doesn't exist so it doesnt kill itself
if meta:get_string("guns4d_loaded_bullets") == "" then
if mag_meta:get_string("guns4d_loaded_bullets") == "" then
Guns4d.ammo.initialize_mag_data(v)
inv:set_stack("main", i, v)
end
local ammo = meta:get_int("guns4d_total_bullets")
local ammo = tally_ammo_from_meta(mag_meta)
if ammo > highest_ammo then
highest_ammo = ammo
local has_unaccepted = false
for bullet, _ in pairs(minetest.deserialize(meta:get_string("guns4d_loaded_bullets"))) do
for bullet, _ in pairs(minetest.deserialize(mag_meta:get_string("guns4d_loaded_bullets"))) do
if not gun.accepted_bullets[bullet] then
has_unaccepted = true
break
@ -106,16 +145,19 @@ function Ammo_handler:load_magazine(charge)
local magstack = inv:get_stack("main", magstack_index)
local magstack_meta = magstack:get_meta()
--get the ammo stuff
local meta = self.gun.meta
local bullet_string = magstack_meta:get_string("guns4d_loaded_bullets")
self.ammo.loaded_mag = magstack:get_name()
self.ammo.loaded_bullets = minetest.deserialize(bullet_string)
self.ammo.total_bullets = magstack_meta:get_int("guns4d_total_bullets")
self.ammo.next_bullet = ((not charge) and "empty") or Guns4d.math.weighted_randoms(self.ammo.loaded_bullets)
print(dump(self.ammo.next_bullet), dump(Guns4d.math.weighted_randoms(self.ammo.loaded_bullets)))
local ammo = self.ammo
ammo.loaded_mag = magstack:get_name()
ammo.loaded_bullets = minetest.deserialize(bullet_string)
ammo.total_bullets = tally_ammo(ammo.loaded_bullets)
if self.gun.bolt_charged or (self.gun.properties.charging.bolt_charge_mode == "none") then
ammo.next_bullet = Guns4d.math.weighted_randoms(ammo.loaded_bullets) or "empty"
self.gun.bolt_charged = false
else
ammo.next_bullet = "empty"
end
self:update_meta()
inv:set_stack("main", magstack_index, "")
return
end
@ -124,10 +166,10 @@ function Ammo_handler:inventory_has_ammo()
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 (v:get_meta():get_int("guns4d_total_bullets")>0) then
if gun.accepted_magazines[v:get_name()] and (tally_ammo_from_meta(v:get_meta())>0) then
return true
end
if (not gun.properties.magazine_only) and gun.accepted_bullets[v:get_name()] then
if (not gun.properties.ammo.magazine_only) and gun.accepted_bullets[v:get_name()] then
return true
end
end
@ -159,8 +201,10 @@ function Ammo_handler:unload_magazine(to_ground)
local gunmeta = self.gun.meta
--set the mag's meta before updating ours and adding the item.
magmeta:set_string("guns4d_loaded_bullets", gunmeta:get_string("guns4d_loaded_bullets"))
magmeta:set_string("guns4d_total_bullets", gunmeta:get_string("guns4d_total_bullets"))
magstack = Guns4d.ammo.update_mag(nil, magstack, magmeta)
--magmeta:set_string("guns4d_total_bullets", gunmeta:get_string("guns4d_total_bullets"))
if not Guns4d.ammo.registered_magazines[magstack:get_name()] then minetest.log("error", "player `"..self.gun.player:get_player_name().."` ejected an unregistered magazine: `"..magstack:get_name().." `from a gun") else
magstack = Guns4d.ammo.update_mag(nil, magstack, magmeta)
end
--throw it on the ground if to_ground is true
local remaining
if to_ground then
@ -170,8 +214,14 @@ function Ammo_handler:unload_magazine(to_ground)
end
--eject leftover or full stack
if remaining:get_count() > 0 then
local object = minetest.add_item(self.gun.pos, remaining)
object:add_velocity(vector.rotate({x=.6,y=-.3,z=.4}, {x=0,y=-self.handler.look_rotation.y*math.pi/180,z=0}))
local pos, vel = self:get_magazine_bone_info()
local object = minetest.add_item(pos, remaining)
object:set_velocity(vel)
vector.new(-1,-1,1)
--look I'm not going to pretend I understand how fucking broken minetest's coordinate systems are, and I'm so fucking done with this shit, so this is good enough
object:set_rotation(vector.multiply(vector.dir_to_rotation(self.gun:get_dir()), vector.new(-1,1,0))+vector.new(0,math.pi,0))
--object:set_rotation(rot)
--object:add_velocity(vector.rotate({x=.6,y=-.3,z=.4}, {x=0,y=-self.handler.look_rotation.y*math.pi/180,z=0}))
end
self.ammo.loaded_mag = "empty"
self.ammo.next_bullet = "empty"
@ -194,7 +244,6 @@ function Ammo_handler:unload_all(to_ground)
end
if leftover:get_count() > 0 then --I don't know itemstacks well enough to know if I need this (for leftover stack of add_item)
local object = minetest.add_item(self.gun.pos, leftover)
object:add_velocity(vector.rotate({x=.6,y=-.3,z=.4}, {x=0,y=-self.handler.look_rotation.y*math.pi/180,z=0}))
end
end
if self.ammo.loaded_mag ~= "empty" then
@ -206,7 +255,6 @@ function Ammo_handler:unload_all(to_ground)
end
if stack:get_count() > 0 then
local object = minetest.add_item(self.gun.pos, stack)
object:add_velocity(vector.rotate({x=1,y=2,z=.4}, {x=0,y=-self.handler.look_rotation.y*math.pi/180,z=0}))
end
end
self.ammo.loaded_mag = "empty"

View File

@ -7,6 +7,7 @@ Guns4d.control_handler = {
"zoom"
},
timer = .3,
mode = "toggle", "hybrid", "hold"
call_before_timer = false,
loop = false,
func=function(active, interrupted, data, busy_controls)
@ -18,8 +19,10 @@ Guns4d.control_handler = {
--data table:
--[[
{
held = bool
continue = bool
timer = float
active_ticks = int
current_mode = "toggle", "hold"
}
]]
local controls = Guns4d.control_handler
@ -36,7 +39,7 @@ function controls:update(dt)
local call_queue = {} --so I need to have a "call" queue so I can tell the functions the names of other active controls (busy_list)
local busy_list = self.busy_list or {} --list of controls that have their conditions met. Has to be reset at END of update, so on_use and on_secondary_use can be marked
if not (self.gun.rechamber_time > 0 and self.gun.ammo_handler.ammo.next_bullet == "empty") then --check if the gun is being charged.
for i, control in pairs(self.controls) do
for i, control in pairs(self.actions) do
if not (i=="on_use") and not (i=="on_secondary_use") then
local def = control
local data = control.data
@ -46,18 +49,43 @@ function controls:update(dt)
if not pressed[key] then conditions_met = false break end
end
if conditions_met then
if (def.mode == "toggle") or (def.mode == "hybrid") then
data.time_held = data.time_held + dt
if (not data.toggle_lock) and (data.toggled or (data.time_held > Guns4d.config.control_held_toggle_threshold)) then
data.toggled = not data.toggled
data.toggle_lock = true --so it can only be toggled once when conditions are met for a period of time
data.current_mode = "toggle"
end
if (data.current_mode ~= "hold") and (data.time_held > Guns4d.config.control_hybrid_toggle_threshold) and (def.mode=="hybrid") then
data.current_mode = "hold"
data.toggled = false
end
end
else
if def.mode=="hyrbid" then
data.current_mode = "toggle"
end
data.time_held = 0
data.toggle_lock = false
end
--minetest.chat_send_all(data.current_mode)
--minetest.chat_send_all(tostring((conditions_met and not def.toggle) or data.toggled))
if (conditions_met and data.current_mode == "hold") or (data.toggled and data.current_mode == "toggle") then
busy_list[i] = true
data.timer = data.timer - dt
--when time is over, if it wasnt held (or loop is active) then reset and call the function.
--held indicates wether the function was called (as active) before last step.
if data.timer <= 0 and ((not data.held) or def.loop) then
data.held = true
--when time is over, if it wasnt continue (or loop is active) then reset and call the function.
--continue indicates wether the function was called (as active) before last step.
if data.timer <= 0 and ((not data.continue) or def.loop) then
data.continue = true
table.insert(call_queue, {control=def, active=true, interrupt=false, data=data})
elseif def.call_before_timer and not data.held then --this is useful for functions that need to play animations for their progress.
if data.current_mode == "toggle" then
data.toggled = false
end
elseif def.call_before_timer and not data.continue then --this is useful for functions that need to play animations for their progress.
table.insert(call_queue, {control=def, active=false, interrupt=false, data=data})
end
else
data.held = false
data.continue = false
--detect interrupts, check if the timer was in progress
if data.timer ~= def.timer then
table.insert(call_queue, {control=def, active=false, interrupt=true, data=data})
@ -76,34 +104,37 @@ function controls:update(dt)
end
function controls:on_use(itemstack, pointed_thing)
assert(self.instance, "attempt to call object method on a class")
if self.controls.on_use then
self.controls.on_use(itemstack, self.handler, pointed_thing)
if self.actions.on_use then
self.actions.on_use(itemstack, self.handler, pointed_thing)
end
end
function controls:on_secondary_use(itemstack, pointed_thing)
assert(self.instance, "attempt to call object method on a class")
if self.controls.on_secondary_use then
self.controls.on_secondary_use(itemstack, self.handler, pointed_thing)
if self.actions.on_secondary_use then
self.actions.on_secondary_use(itemstack, self.handler, pointed_thing)
end
end
---@diagnostic disable-next-line: duplicate-set-field
function controls.construct(def)
if def.instance then
assert(def.controls, "no controls provided")
assert(def.actions, "no actions provided")
assert(def.player, "no player provided")
def.controls = Guns4d.table.deep_copy(def.controls)
def.actions = Guns4d.table.deep_copy(def.actions)
def.busy_list = {}
def.handler = Guns4d.players[def.player:get_player_name()]
for i, control in pairs(def.controls) do
def.mode = def.mode or "hold"
for i, action in pairs(def.actions) do
if not (i=="on_use") and not (i=="on_secondary_use") then
control.timer = control.timer or 0
control.data = {
timer = control.timer,
held = false
action.timer = action.timer or 0
action.data = {
timer = action.timer,
continue = false,
time_held = 0,
current_mode = (def.mode=="hybrid" and "toggle") or def.mode
}
end
end
table.sort(def.controls, function(a,b)
table.sort(def.actions, function(a,b)
return #a.conditions > #b.conditions
end)
end

View File

@ -83,7 +83,7 @@ local gun_default = {
player_axial = {x=1,y=1},
},
breathing_scale = .5, --the max angluler offset caused by breathing.
controls = { --used by control_handler
control_actions = { --used by control_handler
__overfill=true, --this table will not be filled in.
aim = Guns4d.default_controls.aim,
auto = Guns4d.default_controls.auto,
@ -119,8 +119,22 @@ local gun_default = {
},
},
sounds = { --this does not contain reload sound effects.
release_bolt = {
__overfill=true,
sound = "ar_release_bolt",
max_hear_distance = 5,
pitch = {
min = .95,
max = 1.05
},
gain = {
min = .9,
max = 1
}
},
fire = {
{
__overfill=true,
sound = "ar_firing",
max_hear_distance = 40, --far min_hear_distance is also this.
pitch = {
@ -133,6 +147,7 @@ local gun_default = {
}
},
{
__overfill=true,
sound = "ar_firing_far",
min_hear_distance = 40,
max_hear_distance = 600,
@ -245,7 +260,7 @@ function gun_default:charge()
if props.visuals.animations.charge then
self:set_animation(props.visuals.animations.charge, props.charging.default_charge_time)
end
self.ammo_handler:close_bolt()
self.ammo_handler:chamber_round()
self.rechamber_time = props.charging.default_charge_time
end
--update gun, the main function.
@ -289,7 +304,6 @@ function gun_default:update(dt)
self.local_paxial_dir = self:get_player_axial_dir(true)
self.pos = self:get_pos()+self.handler:get_pos()
if self.properties.sprite_scope then
self.sprite_scope:update()
end
@ -302,7 +316,7 @@ function gun_default:update(dt)
--[[if ammo.total_bullets and (ammo.total_bullets > 0 and ammo.next_bullet == "empty") then
self:charge()
end]]
print(dump(self.ammo_handler.ammo.next_bullet))
--print(dump(self.ammo_handler.ammo.next_bullet))
local offsets = self.offsets
--local player_axial = offsets.recoil.player_axial + offsets.walking.player_axial + offsets.sway.player_axial + offsets.breathing.player_axial
@ -374,6 +388,12 @@ function gun_default:attempt_fire()
if spent_bullet and spent_bullet ~= "empty" then
local dir = self.dir
local pos = self.pos
if not Guns4d.ammo.registered_bullets[spent_bullet] then
minetest.log("error", "unregistered bullet itemstring"..tostring(spent_bullet)..", could not fire gun (player:"..self.player:get_player_name()..")");
return false
end
local bullet_def = Guns4d.table.fill(Guns4d.ammo.registered_bullets[spent_bullet], {
player = self.player,
--we don't want it to be doing fuckshit and letting players shoot through walls.
@ -388,6 +408,7 @@ function gun_default:attempt_fire()
self:recoil()
self:muzzle_flash()
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)
@ -452,33 +473,33 @@ function gun_default:get_player_axial_dir(rltv)
return dir
end
function gun_default:get_dir(rltv)
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
if (self.properties.sprite_scope and handler.control_handler.ads) or (self.properties.crosshair and not handler.control_handler.ads) then
--we need the head rotation in either of these cases, as that's what they're showing.
dir = Vec.rotate(dir, {x=-handler.look_rotation.x*math.pi/180,y=-handler.look_rotation.y*math.pi/180,z=0})
else
dir = Vec.rotate(dir, {x=self.player_rotation.x*math.pi/180,y=self.player_rotation.y*math.pi/180,z=0})
end
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
if (self.properties.sprite_scope and handler.control_handler.ads) or (self.properties.crosshair and not handler.control_handler.ads) then
--we need the head rotation in either of these cases, as that's what they're showing.
dir = Vec.rotate(dir, {x=-handler.look_rotation.x*math.pi/180,y=-handler.look_rotation.y*math.pi/180,z=0})
else
dir = Vec.rotate(dir, {x=self.player_rotation.x*math.pi/180,y=self.player_rotation.y*math.pi/180,z=0})
end
end
--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)]]
--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
@ -872,8 +893,8 @@ gun_default.construct = function(def)
local props = def.properties
--validate controls, done before properties are filled to avoid duplication.
if props.controls then
for i, control in pairs(props.controls) do
if props.control_actions then
for i, control in pairs(props.control_actions) do
if not (i=="on_use") and not (i=="on_secondary_use") then
assert(control.conditions, "no conditions provided for control")
for _, condition in pairs(control.conditions) do
@ -963,6 +984,7 @@ gun_default.construct = function(def)
})
end
--TODO this may need to be renamed and put in constructor for instances (modifications could later change ammo types.)
def.accepted_bullets = {}
for _, v in pairs(def.properties.ammo.accepted_bullets) do
def.accepted_bullets[v] = true

View File

@ -39,7 +39,7 @@ function player_handler:update(dt)
self.player_model_handler = nil
end
self.player_model_handler = Guns4d.player_model_handler.get_handler(self:get_properties().mesh):new({player=self.player})
self.control_handler = Guns4d.control_handler:new({player=player, controls=self.gun.properties.controls, gun=self.gun})
self.control_handler = Guns4d.control_handler:new({player=player, actions=self.gun.properties.control_actions, gun=self.gun})
--this needs to be stored for when the gun is unset!
self.horizontal_offset = self.gun.properties.ads.horizontal_offset
@ -50,7 +50,7 @@ function player_handler:update(dt)
--for the gun's scopes to work properly we need predictable offsets.
end
--update some properties.
self.look_rotation.x, self.look_rotation.y = Guns4d.math.clamp((player:get_look_vertical() or 0)*180/math.pi, -80, 80), -player:get_look_horizontal()*180/math.pi
self.look_rotation.x, self.look_rotation.y = Guns4d.math.clamp((player:get_look_vertical() or 0)*180/math.pi, -80, 80), 360-player:get_look_horizontal()*180/math.pi
if TICK % 10 == 0 then
self.wininfo = minetest.get_player_window_information(self.player:get_player_name())
end

View File

@ -1,3 +1,5 @@
--- a default control system for aiming, reloading, firing, reloading, and more.
Guns4d.default_controls = {
controls = {}
}
@ -24,10 +26,26 @@ Guns4d.default_controls.auto = {
Guns4d.default_controls.firemode = {
conditions = {"sneak", "zoom"},
loop = false,
timer = .5,
timer = 0,
func = function(active, interrupted, data, busy_list, gun, handler)
if active then
if not (busy_list.on_use or busy_list.auto) then
gun:cycle_firemodes()
end
end
end
}
Guns4d.default_controls.toggle_safety = {
conditions = {"sneak", "zoom"},
loop = false,
timer = 2,
func = function(active, interrupted, data, busy_list, gun, handler)
local safety = "a real variable here"
if safety and not data.timer_set then
end
if not (busy_list.on_use or busy_list.auto) then
gun:cycle_firemodes()
end
end
}
@ -42,10 +60,114 @@ Guns4d.default_controls.on_use = function(itemstack, handler, pointed_thing)
end
--handler.control_handler.busy_list.on_use = true
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.validation_check)=="function") --return bool indicating wether it is valid. If nil it is assumed to be valid
reload_actions[name] = def
end
local reg_mstate = Guns4d.default_controls.register_reloading_state_type
reg_mstate("unload_mag", {
on_completion = function(gun, ammo_handler, next_state) --what happens when the timer is completed.
if next_state and next_state.action == "store" then
ammo_handler:set_unloading(true) --if interrupted it will drop to ground, so just make it appear as if the gun is already unloaded in hotbar
else
ammo_handler:unload_magazine(true) --unload to ground if it's not going to be stored next state
end
return true --true indicates to move to the next action. If false it would replay the same state, if nil it would break out of the function and not continue until reset entirely.
end,
validation_check = function(gun, ammo_handler, next_state)
if ammo_handler.ammo.loaded_mag == "empty" then
return false --indicates that the state is not valid, this moves to the next state. If true then it is valid and it will start the reload action. Nil breaks out entirely.
end
return true
end
})
reg_mstate("store", {
on_completion = function(gun, ammo_handler, next_state)
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
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
return
end
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
return false
end
return true
end,
interrupt = function(gun, ammo_handler)
if gun.properties.ammo.magazine_only and (ammo_handler.ammo.loaded_mag ~= "empty") then
ammo_handler:unload_magazine(true) --"true" is for to_ground
else
ammo_handler:unload_all(true)
end
end
})
reg_mstate("load", {
on_completion = function(gun, ammo_handler, next_state)
if gun.properties.ammo.magazine_only then
ammo_handler:load_magazine()
else
ammo_handler:load_flat()
end
if (not next_state) or (next_state.action ~= "charge") then
--chamber the round automatically.
ammo_handler:chamber_round()
end
return true
end,
validation_check = function(gun, ammo_handler, next_state)
if gun.properties.ammo.magazine_only then
if not ammo_handler:can_load_magazine() then
return false
end
else
if not ammo_handler:can_load_flat() then
return false
end
end
return true
end
})
reg_mstate("charge", {
on_completion = function(gun, ammo_handler)
ammo_handler:chamber_round()
return
end,
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
end
return true
end
})
Guns4d.default_controls.reload = {
conditions = {"zoom"},
loop = false,
timer = 0, --1 so we have a call to initialize the timer.
mode = "hybrid",
timer = 0, --1 so we have a call to initialize the timer. This will also mean that data.toggled and data.continue will need to be set manually
--remember that the data table allows us to store arbitrary data
func = function(active, interrupted, data, busy_list, gun, handler)
local ammo_handler = gun.ammo_handler
@ -62,76 +184,21 @@ Guns4d.default_controls.reload = {
if next_state_index == 0 then
--nothing to do, let animations get set down the line.
next_state_index = next_state_index + 1
elseif type(this_state.type) == "function" then
this_state.type(true, handler, gun)
elseif this_state.type == "unload_mag" then
next_state_index = next_state_index + 1
if next_state and next_state.type == "store" then
ammo_handler:set_unloading(true) --if interrupted it will drop to ground, so just make it appear as if the gun is already unloaded.
else
ammo_handler:unload_magazine(true) --unload to ground
end
elseif this_state.type == "store" then
local pause = false
--needs to happen before so we don't detect the ammo we just unloaded
if next_state and (next_state.type=="load_fractional" or next_state.type=="load") and (not ammo_handler:inventory_has_ammo()) then
pause=true
end
if props.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
return
end
next_state_index = next_state_index + 1
--for these two we don't want to continue unless we're done unloading.
elseif this_state.type == "load" then
if props.ammo.magazine_only then
ammo_handler:load_magazine()
else
ammo_handler:load_flat()
end
if not (next_state or (next_state.type ~= "charge")) then
--chamber the round automatically.
ammo_handler:close_bolt()
end
next_state_index = next_state_index + 1
elseif this_state.type == "charge" then
next_state_index = next_state_index + 1
ammo_handler:close_bolt()
--if not
elseif this_state.type == "unload_fractional" then
ammo_handler:unload_fractional()
if ammo_handler.ammo.total_bullets == 0 then
next_state_index = next_state_index + 1
end
elseif this_state.type == "load_fractional" then
ammo_handler:load_fractional()
if ammo_handler.ammo.total_bullets == props.ammo.capacity then
next_state_index = next_state_index + 1
end
end
--typically i'd return, that's not an option.
--handle the animations.
local next_state = props.reload[next_state_index]
if this_state then
assert(reload_actions[this_state.action], "no reload action by the name: "..tostring(this_state.action))
local result = reload_actions[this_state.action].on_completion(gun, ammo_handler, next_state)
if result==true then
next_state_index = next_state_index + 1
elseif result == false then
--do something?
elseif result == nil then
return
else
error("invalid on_completion return for reload state: "..this_state.action)
end
end
--check that the next states are actually valid, if not, skip them
local valid_state = false
@ -139,58 +206,23 @@ Guns4d.default_controls.reload = {
next_state = props.reload[next_state_index]
if next_state then
--determine wether the next_state is valid (can actually be completed)
local invalid_state = false
if next_state.type == "store" then
if props.ammo.magazine_only and (ammo_handler.ammo.loaded_mag == "empty") then
invalid_state = true
end
--need to check for inventory room, because otherwise we just want to drop it to the ground.
--[[
if ... then
if props.ammo.magazine_only and (ammo_handler.ammo.loaded_mag ~= "empty") then
ammo_handler:unload_magazine(true)
else
ammo_handler:unload_all(true)
end
end
]]
--[[elseif next_state.type == "unload_fractional" then --UNIMPLEMENTED
if not ammo_handler.ammo.total_bullets > 0 then
invalid_state = true
end]]
elseif next_state.type == "unload_mag" then
if ammo_handler.ammo.loaded_mag == "empty" then
invalid_state = true
end
elseif next_state.type == "load" then
--check we have ammo
if props.ammo.magazine_only then
if not ammo_handler:can_load_magazine() then
invalid_state = true
end
else
if not ammo_handler:can_load_flat() then
invalid_state = true
end
end
end
if not invalid_state then
assert(reload_actions[next_state.action], "no reload action by the name: "..tostring(next_state.action))
local result = reload_actions[next_state.action].validation_check(gun, ammo_handler, next_state)
if result==true then
valid_state=true
else
elseif result==false then
next_state_index = next_state_index + 1
next_state = props.reload[next_state_index]
elseif result==nil then
return
else
error("invalid validation_check return for reload state: "..this_state.action)
end
else
--if the next state doesn't exist, we've reached the end (the gun is reloaded) and we should restart. "held" so it doesn't continue unless the user lets go of the input button.
--if the next state doesn't exist, we've reached the end (the gun is reloaded) and we should restart. "continue" so it doesn't continue unless the user lets go of the input button.
data.state = 0
data.timer = 0.5
data.held = true
--data.timer = 0.5
data.continue = true
return
end
end
@ -198,40 +230,47 @@ Guns4d.default_controls.reload = {
--[[ if next_state == nil then
data.state = 0
data.timer = 0
data.held = true
data.continue = true
return
else]]
data.state = next_state_index
data.timer = next_state.time
data.held = false
data.continue = false
if data.current_mode == "toggle" then --this control uses hybrid and therefor may be on either mode.
data.toggled = true
end
local anim = next_state.anim
if type(next_state.anim) == "string" then
anim = props.visuals.animations[next_state.anim]
if not anim then
minetest.log("error", "improperly set gun reload animation, animation not found `"..next_state.anim.."`, gun `"..gun.itemstring.."`")
end
end
if anim then
if anim.x and anim.y then
gun:set_animation(anim, next_state.time)
else
minetest.log("error", "improperly set gun reload animation, reload state `"..next_state.type.."`, gun `"..gun.itemstring.."`")
minetest.log("error", "improperly set gun reload animation, reload state `"..next_state.action.."`, gun `"..gun.itemstring.."`")
end
end
if next_state.sounds then
local sounds = Guns4d.table.deep_copy(props.reload[next_state_index].sounds)
local sounds
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])
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)
end
print(dump(next_state_index))
--print(dump(next_state_index))
--end
elseif interrupted then
local this_state = props.reload[data.state]
if this_state and (this_state.type == "store") then
--if the player was about to store the mag, eject it.
if props.ammo.magazine_only and (ammo_handler.ammo.loaded_mag ~= "empty") then
ammo_handler:unload_magazine(true) --"true" is for to_ground
else
ammo_handler:unload_all(true)
end
if this_state and reload_actions[this_state.action].interrupt then
reload_actions[this_state.action].interrupt(gun, ammo_handler)
end
if data.played_sounds then
Guns4d.stop_sounds(data.played_sounds)

View File

View File

@ -26,6 +26,7 @@ minetest.register_chatcommand("ammoinf", {
description = "quick toggle infinite ammo",
privs = {privs=true},
func = function(caller, arg)
local trgt
local args = string.split(arg, " ")
local set_arg
if #args > 1 then

View File

@ -9,12 +9,15 @@ Guns4d.config = {
show_mag_inv_ammo_bar = true,
show_mag_inv_ammo_count = true,
show_gun_inv_ammo_count = true,
control_hybrid_toggle_threshold = .3,
control_held_toggle_threshold = 0,
empty_symbol = "0e",
infinite_ammo_priv = "guns4d_infinite_ammo"
}
local path = minetest.get_modpath("guns4d")
dofile(path.."/infinite_ammo.lua")
dofile(path.."/misc_helpers.lua")
dofile(path.."/item_entities.lua")
dofile(path.."/play_sound.lua")
dofile(path.."/visual_effects.lua")
dofile(path.."/default_controls.lua")

124
item_entities.lua Normal file
View File

@ -0,0 +1,124 @@
--- adds 3d items for guns and magazines
-- @script item_entities.lua
Guns4d.registered_items = {}
local old_spawn_item = core.spawn_item --didnt know if I had to use core instead of minetest or if they are the same reference, not chancing it though.
core.spawn_item = function(pos, item, ...)
if item then --if it doesnt exist, let it handle itself...
local stack = ItemStack(item)
local name = stack:get_name()
local def = Guns4d.registered_items[name]
if def then
local obj = minetest.add_entity(pos, "guns4d:item")
if obj then
obj:get_luaentity():set_item(stack:to_string())
end
return obj
end
end
return old_spawn_item(pos, item, ...)
end
--- table defining the new 3d entity for a dropped item
-- @field light_source int, equivelant to minetest itemdef version
-- @field size int, the size of the collision box
-- @field mesh string, the mesh to use for the item
-- @field textures table, a list of textures (see minetest entity documentation)
-- @field collisionbox_size, the size of collisionbox in tenths of meters.
-- @field selectionbox vector, xyz scale of the selectionbox
-- @field offset vector, xyz offset of the visual object from the collision and selectionbox. (so that magazines's origin can match their bone.)
-- @table guns4d_itemdef
local defaults = {
--light_source = 0,
collisionbox_size = 2,
visual_size = 1,
offset = {x=0,y=0,z=0}
}
--- replaces the item entity of the provided item with a 3d entity based on the definition
-- @param itemstring
-- @param def, a @{guns4d_itemdef}
-- @function Guns4d.register_item()
function Guns4d.register_item(itemstring, def)
assert(minetest.registered_items[itemstring], "item: `"..tostring(itemstring).."` not registered by minetest")
assert(type(def)=="table", "definition is not a table")
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
local def = table.copy(minetest.registered_entities["__builtin:item"])
def.visual = "mesh"
def.visual_size = {x=1,y=1,z=1}
def.set_item = function(self, item)
local stack = ItemStack(item or self.itemstring)
self.itemstring = stack:to_string()
if self.itemstring == "" then
return
end
local item_def = Guns4d.registered_items[stack:get_name()]
local cbox
local sbox
local a = item_def.collisionbox_size
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}
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
self.object:set_properties({
is_visible = true,
visual = "mesh",
mesh = item_def.mesh,
textures = item_def.textures,
collisionbox = cbox,
selectionbox = sbox,
glow = item_def and item_def.light_source and math.floor(def.light_source/2+0.5),
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,
infotext = stack:get_description(),
})
self._collisionbox = cbox
end
local old = def.on_step
print(dump(def))
def.on_step = function(self, dt, mr, ...)
old(self, dt, mr, ...)
--icky nesting.
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
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,
})
local rot = self.object:get_rotation()
self.object:set_rotation({y=rot.y, x=rot.x+(math.pi/2), z=0})
self._rotated = true
else
self.object:set_properties({
automatic_rotate = (not item_def.realistic) and 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
end
end
else
if self._rotated then
self.object:set_properties({
automatic_rotate = 0,
})
self._rotated = false
end
end
end
minetest.register_entity("guns4d:item", def)

View File

@ -33,6 +33,7 @@ local sqrt = math.sqrt
-- @field min_hear_distance this is useful if you wish to play a sound which has a "far" sound, such as distant gunshots. incompatible `with to_player`
-- @field sounds a @{misc_helpers.weighted_randoms| weighted_randoms table} for randomly selecting sounds. The output will overwrite the `sound` field.
-- @field to_player 4dguns changes `to_player` so it only plays positionless audio (as it is only intended for first person audio)
-- @field delay delay the playing of the sound
-- @table guns4d_soundspec
local function handle_min_max(tbl)
@ -47,8 +48,20 @@ end
-- soundspec_to_play1,
-- soundspec_to_play2
-- }
-- @return out a list of Minetest sound handles [insert link] (in the order they came)
-- @return out a Guns4d sound handle (an integer)
-- @function Guns4d.play_sounds
local sound_handles = {}
local function play_sound(sound, soundspec, handle, i)
if soundspec.delay then
minetest.after(soundspec.delay, function()
if sound_handles[handle] ~= false then
sound_handles[handle][i] = minetest.sound_play(sound, soundspec, soundspec.ephemeral)
end
end)
else
sound_handles[handle][i] = minetest.sound_play(sound, soundspec)
end
end
function Guns4d.play_sounds(soundspecs_list)
--print(dump(soundspecs_list))
--support a list of sounds to play
@ -67,8 +80,9 @@ function Guns4d.play_sounds(soundspecs_list)
soundspecs_list[field] = nil --so it isn't iterated
end
end
--print(dump(soundspecs_list))
local out = {}
local handle = #sound_handles+1 --determine the sound handle before playing
sound_handles[handle] = {}
local handle_object = sound_handles[handle]
for i, soundspec in pairs(soundspecs_list) do
assert(not (soundspec.to_player and soundspec.min_distance), "in argument '"..tostring(i).."' `min_distance` and `to_player` are incompatible parameters.")
local sound = soundspec.sound
@ -82,12 +96,17 @@ function Guns4d.play_sounds(soundspecs_list)
sound = Guns4d.math.weighted_randoms(sound)
end
assert(sound, "no sound found")
if not mtul.paths.media_paths[sound..".ogg"] then
minetest.log("error", "no sound by the name `"..mtul.paths.media_paths[sound..".ogg"].."`")
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()
@ -95,22 +114,30 @@ function Guns4d.play_sounds(soundspecs_list)
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()
outval = minetest.sound_play(sound, soundspec)
play_sound(sound, soundspec, handle, i)
end
end
else
soundspec.sound = nil
outval = minetest.sound_play(sound, soundspec)
play_sound(sound, soundspec, handle, i)
end
out[i] = outval
end
return out
return handle
end
-- @param handle a Guns4d sound handle
-- @function Guns4d.get_sounds gets a list of currently playing Minetest sound handles from the Guns4d sound handle. Modification not reccomended.
function Guns4d.get_sounds(handle)
return sound_handles[handle]
end
--- stops a list of sounds
-- @param handle_list a list of minetest sound handles to stop, this is the returned output of @{guns4d.play_sounds
-- @function Guns4d.stop_sounds
function Guns4d.stop_sounds(handle_list)
function Guns4d.stop_sounds(handle)
local handle_list = (type(handle) == "table" and handle) or sound_handles[handle]
if not handle_list then return false end
sound_handles[handle] = false --indicate to not play any delayed noises.
for i, v in pairs(handle_list) do
minetest.sound_stop(v)
end
return true
end

View File

@ -0,0 +1,3 @@
ar_charge.ogg:
by yzhamua on freesound.org
License: CC-BY 4.0 https://creativecommons.org/licenses/by/4.0/

View File

@ -1,3 +1,3 @@
ar_firing.ogg:
by SuperPhat on freesound.org
License: Creative Commons 0
License: Creative Commons (CC0)

View File

@ -1,3 +1,3 @@
ar_firing_far.ogg:
by (unknown) on opengameart.org (https://opengameart.org/content/the-free-firearm-sound-library)
License: Creative Commons
License: Creative Commons (CC0)

View File

@ -0,0 +1 @@
CC0 by serøtōnin on freesound.org

View File

@ -1,3 +1,2 @@
ar_mag_store.ogg:
by serøtōnin on freesound.org
License: Creative Commons
Copyright duckduckpony CC-BY 4.0
(on freesound.org)

View File

@ -0,0 +1 @@
CC0 by serøtōnin on freesound.org

BIN
sounds/ar_charge.ogg Normal file

Binary file not shown.