From 1cc80167328f0e0afc6eba901b8322b5b9ae0d31 Mon Sep 17 00:00:00 2001 From: FatalErr42O <58855799+FatalError42O@users.noreply.github.com> Date: Sun, 31 Mar 2024 21:27:07 -0700 Subject: [PATCH] added bullet wizz --- block_values.lua | 2 +- classes/Bullet_ray.lua | 118 ++++++++++++++---- init.lua | 4 +- misc_helpers.lua | 13 ++ play_sound.lua | 11 +- sounds/{ => ar}/LICENSE ar_charge.ogg.txt | 0 sounds/{ => ar}/LICENSE ar_firing.ogg.txt | 0 sounds/{ => ar}/LICENSE ar_firing_far.ogg.txt | 0 sounds/{ => ar}/LICENSE ar_mag_load.ogg.txt | 0 sounds/{ => ar}/LICENSE ar_mag_store.ogg.txt | 0 sounds/{ => ar}/LICENSE ar_mag_unload.ogg.txt | 0 sounds/{ => ar}/ar_charge.ogg | Bin sounds/{ => ar}/ar_firing.ogg | Bin sounds/{ => ar}/ar_firing_far.ogg | Bin sounds/{ => ar}/ar_mag_load.ogg | Bin sounds/{ => ar}/ar_mag_store.ogg | Bin sounds/{ => ar}/ar_mag_unload.ogg | Bin sounds/{ => ar}/attribution and licensing.txt | 0 sounds/elkien/bullet_crack.ogg | Bin 0 -> 6646 bytes sounds/elkien/bullet_whizz.ogg | Bin 0 -> 4634 bytes sounds/elkien/license.txt | 14 +++ 21 files changed, 133 insertions(+), 29 deletions(-) rename sounds/{ => ar}/LICENSE ar_charge.ogg.txt (100%) rename sounds/{ => ar}/LICENSE ar_firing.ogg.txt (100%) rename sounds/{ => ar}/LICENSE ar_firing_far.ogg.txt (100%) rename sounds/{ => ar}/LICENSE ar_mag_load.ogg.txt (100%) rename sounds/{ => ar}/LICENSE ar_mag_store.ogg.txt (100%) rename sounds/{ => ar}/LICENSE ar_mag_unload.ogg.txt (100%) rename sounds/{ => ar}/ar_charge.ogg (100%) rename sounds/{ => ar}/ar_firing.ogg (100%) rename sounds/{ => ar}/ar_firing_far.ogg (100%) rename sounds/{ => ar}/ar_mag_load.ogg (100%) rename sounds/{ => ar}/ar_mag_store.ogg (100%) rename sounds/{ => ar}/ar_mag_unload.ogg (100%) rename sounds/{ => ar}/attribution and licensing.txt (100%) create mode 100644 sounds/elkien/bullet_crack.ogg create mode 100644 sounds/elkien/bullet_whizz.ogg create mode 100644 sounds/elkien/license.txt diff --git a/block_values.lua b/block_values.lua index fbd2674..8ad57fe 100644 --- a/block_values.lua +++ b/block_values.lua @@ -26,7 +26,7 @@ minetest.register_on_mods_loaded(function() RHA = RHA / groups.oddly_breakable_by_hand end if groups.choppy then - RHA = RHA/(5*groups.choppy) + RHA = RHA/(10*groups.choppy) end if groups.flora or groups.grass then RHA = 0 diff --git a/classes/Bullet_ray.lua b/classes/Bullet_ray.lua index 8c7fe78..1f81d1f 100644 --- a/classes/Bullet_ray.lua +++ b/classes/Bullet_ray.lua @@ -12,8 +12,38 @@ local ray = { sharp_to_blunt_conversion_factor = .5, -- 1mmRHA is converted to 1mPA of blunt force blunt_damage_groups = {}, --minetest.deserialize(Guns4d.config.default_blunt_groups), --these are multiplied by blunt_damage sharp_damage_groups = {}, --minetest.deserialize(Guns4d.config.default_sharp_groups), - ITERATION_DISTANCE = .3, - damage = 0 + pass_sounds = { + --[1] will be preferred if present + supersonic = { + sound = "bullet_crack", + max_hear_distance = 3, + pitch = { + min = .6, + max = 1.5 + }, + gain = { + min = .9, --this uses distance instead of randomness + max = .4 + } + }, + subsonic = { + sound = "bullet_whizz", + max_hear_distance = 3, + pitch = { + min = .5, + max = 1.5 + }, + gain = { + min = .3, --this uses distance instead of randomness + max = .9 + } + }, + }, + supersonic_energy = Guns4d.config.minimum_supersonic_energy_assumption, + pass_sound_max_distance = 3, + damage = 0, + energy = 0, + ITERATION_DISTANCE = Guns4d.config.default_penetration_iteration_distance, } --find (valid) edge. Slabs or other nodeboxes that are not the last hit position are not considered (to account for holes) TODO: update to account for hollow nodes @@ -250,48 +280,90 @@ function ray:bullet_hole(pos, normal) Guns4d.effects.spawn_bullet_hole_particle(pos, self.hole_scale, '(bullet_hole_1.png^(bullet_hole_2.png^[opacity:129))') end end +function ray:play_bullet_pass_sounds() + --iteration done, damage applied, find players to apply bullet whizz to + local start_pos = self.init_pos + local played_for = {} + for i = #self.history, 1, -1 do + local v = self.history[i] + for _, player in pairs(minetest.get_connected_players()) do + if (player~=self.player) and not played_for[player] then + local pos = player:get_pos()+vector.new(0,player:get_properties().eye_height,0) + local nearest = Guns4d.nearest_point_on_line(start_pos, v.pos, pos) + if vector.distance(nearest, pos) < self.pass_sound_max_distance then + played_for[player] = true + if self.pass_sounds[1] then + local sound = Guns4d.table.deep_copy(self.pass_sounds[1]) + sound.pos = nearest + Guns4d.play_sounds(self.pass_sounds[1]) + else + --interpolate to find the energy of the shot to determine supersonic or not. + local v1 + if #self.history > i then v1 = v[i+1].energy else v1 = self.init_energy end + local v2 = v.energy + + local ratio = vector.distance(start_pos, nearest)/vector.distance(start_pos, pos) + local energy_at_point = v1+((v2-v1)*(1-ratio)) + + local sound = self.pass_sounds.subsonic + if energy_at_point >= self.supersonic_energy then + sound = self.pass_sounds.supersonic + end + sound = Guns4d.table.deep_copy(sound) + sound.pos = nearest + for _, t in pairs({"gain", "pitch"}) do + if sound[t].min then + sound[t] = sound[t].max+((sound[t].min-sound[t].max)*(vector.distance(nearest, pos)/self.pass_sound_max_distance)) + end + end + Guns4d.play_sounds(sound) + end + end + end + end + start_pos = v.pos + end +end function ray.construct(def) if def.instance then - assert(def.player, "no player") + --these asserts aren't necessary, probably drags down performance a tiny bit. + + --[[assert(def.player, "no player") assert(def.pos, "no position") assert(def.dir, "no direction") assert(def.gun, "no Gun object") assert(def.range, "no range") assert(def.energy, "no energy") - assert(def.energy_dropoff, "no energy dropoff") + assert(def.energy_dropoff, "no energy dropoff")]] --use this if you don't want to use the built-in system for penetrations. - assert(not(def.ignore_penetration and not rawget(def, "hit_entity")), "bullet ray cannot ignore default penetration if hit_entity() is undefined. Use ignore_penetration for custom damage systems." ) - if not def.ignore_penetration then - assert((not (def.blunt_penetration and def.energy)) or (def.blunt_penetration < def.energy), "blunt penetration may not be greater than energy! Blunt penetration is in Joules/Megapascals, energy is also in Joules.") + -- assert((not (def.blunt_penetration and def.energy)) or (def.blunt_penetration < def.energy), "blunt penetration may not be greater than energy! Blunt penetration is in Joules/Megapascals, energy is also in Joules.") - --"raw" damages define the damage (unaffected by armor groups) for the initial penetration value of each type. - --def.sharp_damage_groups = {} --tool capabilities - --def.blunt_damage_groups = {} - - --guns4d mmRHA is used in traditional context. - assert((not def.blunt_damage_groups) or not def.blunt_damage_groups["guns4d_mmRHA"], "guns4d_mmRHA damage group is not used in a traditional context. To increase penetration, increase sharp_penetration field.") - assert((not def.blunt_damage_groups) or not def.blunt_damage_groups["guns4d_Pa"], "guns4d_Pa is not used in a traditional context. To increase blunt penetration, increase blunt_penetration field.") + --guns4d mmRHA is used in traditional context. + --assert((not def.blunt_damage_groups) or not def.blunt_damage_groups["guns4d_mmRHA"], "guns4d_mmRHA damage group is not used in a traditional context. To increase penetration, increase sharp_penetration field.") + --assert((not def.blunt_damage_groups) or not def.blunt_damage_groups["guns4d_Pa"], "guns4d_Pa is not used in a traditional context. To increase blunt penetration, increase blunt_penetration field.") - def.raw_sharp_damage = def.raw_sharp_damage or 0 - def.raw_blunt_damage = def.raw_blunt_damage or 0 - def.sharp_penetration = def.sharp_penetration or 0 - if def.sharp_penetration==0 then - def.blunt_penetration = def.blunt_penetration or def.energy/2 - else - def.blunt_penetration = def.blunt_penetration or def.energy - end - def.energy_sharp_ratio = (def.energy-def.blunt_penetration)/def.energy + def.raw_sharp_damage = def.raw_sharp_damage or 0 + def.raw_blunt_damage = def.raw_blunt_damage or 0 + def.sharp_penetration = def.sharp_penetration or 0 + if def.sharp_penetration==0 then + def.blunt_penetration = def.blunt_penetration or def.energy/2 + else + def.blunt_penetration = def.blunt_penetration or def.energy end + def.energy_sharp_ratio = (def.energy-def.blunt_penetration)/def.energy + def.init_energy = def.energy --blunt pen is in the same units (1 Joule/Area^3 = 1 Pa), so we use it to make the ratio by subtraction. def.dir = vector.new(def.dir) def.pos = vector.new(def.pos) def.history = {} + def.init_pos = vector.new(def.pos) --has to be cloned before iteration def:_iterate() + def:play_bullet_pass_sounds() end end Guns4d.bullet_ray = Instantiatable_class:inherit(ray) \ No newline at end of file diff --git a/init.lua b/init.lua index 022af0f..a53882a 100644 --- a/init.lua +++ b/init.lua @@ -22,6 +22,8 @@ Guns4d.config = { default_fov = 80, headshot_damage_factor = 1.75, enable_touchscreen_command_name = "guns4d_enable_touchmode", + minimum_supersonic_energy_assumption = 900, --used to determine the energy of a "supersonic" bullet for bullet whizzing sound effects + default_penetration_iteration_distance = .25, --`["official_content.replace_ads_with_bloom"] = false, --`["official_content.uses_magazines"] = true } @@ -29,7 +31,7 @@ local path = minetest.get_modpath("guns4d") print("file read?") local conf = Settings(path.."/guns4d_settings.conf"):to_table() or {} -local mt_conf = minetest.settings:to_table() +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 --Guns4d.config[i] = conf[i] or minetest.settings["guns4d."..i] or Guns4d.config[i] --cant use or because it'd evaluate to false if the setting is alse diff --git a/misc_helpers.lua b/misc_helpers.lua index 794c345..b6f1cfb 100644 --- a/misc_helpers.lua +++ b/misc_helpers.lua @@ -283,4 +283,17 @@ function Guns4d.rltv_point_to_hud(pos, fov, aspect) local y = (pos.y/pos.z)*a6 local z = (pos.z/pos.z)*a11 return {x=x / 2,y=-y / 2} --output needs to be offset by +.5 on both for HUD elements, but this cannot be integrated. +end + +--Code: Elkien3 (CC BY-SA 3.0) +--https://github.com/Elkien3/spriteguns/blob/1c632fe12c35c840d6c0b8307c76d4dfa44d1bd7/init.lua#L76 +function Guns4d.nearest_point_on_line(lineStart, lineEnd, pnt) + local line = vector.subtract(lineEnd, lineStart) + local len = vector.length(line) + line = vector.normalize(line) + + local v = vector.subtract(pnt, lineStart) + local d = vector.dot(v, line) + d = Guns4d.math.clamp(d, 0, len); + return vector.add(lineStart, vector.multiply(line, d)) end \ No newline at end of file diff --git a/play_sound.lua b/play_sound.lua index 8f7ed1a..fdb726a 100644 --- a/play_sound.lua +++ b/play_sound.lua @@ -32,8 +32,10 @@ local sqrt = math.sqrt -- however has the following changed or guns4d specific parameters. -- @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 to_player 4dguns changes `to_player` so it only plays positionless audio (as it is only intended for first person audio). If set to string "from_player" and player present +-- @field player this is so to_player being set to "from_player". It's to be set to the player which fired the weapon. -- @field delay delay the playing of the sound +-- @field has_speed_of_sound = true -- @table guns4d_soundspec local function handle_min_max(tbl) @@ -84,6 +86,7 @@ function Guns4d.play_sounds(soundspecs_list) sound_handles[handle] = {} local handle_object = sound_handles[handle] for i, soundspec in pairs(soundspecs_list) do + if soundspec.to_player == "from_player" then soundspec.to_player = soundspec.player:get_player_name() end --setter of sound may not have access to this info, so add a method to use it. assert(not (soundspec.to_player and soundspec.min_distance), "in argument '"..tostring(i).."' `min_distance` and `to_player` are incompatible parameters.") local sound = soundspec.sound local outval @@ -95,9 +98,9 @@ function Guns4d.play_sounds(soundspecs_list) if type(sound) == "table" then 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"].."`") + assert(sound, "no sound provided") + if not mtul.paths.media_paths[(sound or "[NIL]")..".ogg"] then + minetest.log("error", "no sound by the name `"..mtul.paths.media_paths[(sound or "[NIL]")..".ogg"].."`") end --print(dump(soundspecs_list), i) if soundspec.to_player then soundspec.pos = nil end diff --git a/sounds/LICENSE ar_charge.ogg.txt b/sounds/ar/LICENSE ar_charge.ogg.txt similarity index 100% rename from sounds/LICENSE ar_charge.ogg.txt rename to sounds/ar/LICENSE ar_charge.ogg.txt diff --git a/sounds/LICENSE ar_firing.ogg.txt b/sounds/ar/LICENSE ar_firing.ogg.txt similarity index 100% rename from sounds/LICENSE ar_firing.ogg.txt rename to sounds/ar/LICENSE ar_firing.ogg.txt diff --git a/sounds/LICENSE ar_firing_far.ogg.txt b/sounds/ar/LICENSE ar_firing_far.ogg.txt similarity index 100% rename from sounds/LICENSE ar_firing_far.ogg.txt rename to sounds/ar/LICENSE ar_firing_far.ogg.txt diff --git a/sounds/LICENSE ar_mag_load.ogg.txt b/sounds/ar/LICENSE ar_mag_load.ogg.txt similarity index 100% rename from sounds/LICENSE ar_mag_load.ogg.txt rename to sounds/ar/LICENSE ar_mag_load.ogg.txt diff --git a/sounds/LICENSE ar_mag_store.ogg.txt b/sounds/ar/LICENSE ar_mag_store.ogg.txt similarity index 100% rename from sounds/LICENSE ar_mag_store.ogg.txt rename to sounds/ar/LICENSE ar_mag_store.ogg.txt diff --git a/sounds/LICENSE ar_mag_unload.ogg.txt b/sounds/ar/LICENSE ar_mag_unload.ogg.txt similarity index 100% rename from sounds/LICENSE ar_mag_unload.ogg.txt rename to sounds/ar/LICENSE ar_mag_unload.ogg.txt diff --git a/sounds/ar_charge.ogg b/sounds/ar/ar_charge.ogg similarity index 100% rename from sounds/ar_charge.ogg rename to sounds/ar/ar_charge.ogg diff --git a/sounds/ar_firing.ogg b/sounds/ar/ar_firing.ogg similarity index 100% rename from sounds/ar_firing.ogg rename to sounds/ar/ar_firing.ogg diff --git a/sounds/ar_firing_far.ogg b/sounds/ar/ar_firing_far.ogg similarity index 100% rename from sounds/ar_firing_far.ogg rename to sounds/ar/ar_firing_far.ogg diff --git a/sounds/ar_mag_load.ogg b/sounds/ar/ar_mag_load.ogg similarity index 100% rename from sounds/ar_mag_load.ogg rename to sounds/ar/ar_mag_load.ogg diff --git a/sounds/ar_mag_store.ogg b/sounds/ar/ar_mag_store.ogg similarity index 100% rename from sounds/ar_mag_store.ogg rename to sounds/ar/ar_mag_store.ogg diff --git a/sounds/ar_mag_unload.ogg b/sounds/ar/ar_mag_unload.ogg similarity index 100% rename from sounds/ar_mag_unload.ogg rename to sounds/ar/ar_mag_unload.ogg diff --git a/sounds/attribution and licensing.txt b/sounds/ar/attribution and licensing.txt similarity index 100% rename from sounds/attribution and licensing.txt rename to sounds/ar/attribution and licensing.txt diff --git a/sounds/elkien/bullet_crack.ogg b/sounds/elkien/bullet_crack.ogg new file mode 100644 index 0000000000000000000000000000000000000000..fe2ff8beabc359270dae7471c52b85e2b4ffcd90 GIT binary patch literal 6646 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 literal 0 HcmV?d00001 diff --git a/sounds/elkien/bullet_whizz.ogg b/sounds/elkien/bullet_whizz.ogg new file mode 100644 index 0000000000000000000000000000000000000000..7df8512cc4ad283ce43d2f104542584fb2e99a15 GIT binary patch literal 4634 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