diff --git a/classes/Ammo_handler.lua b/classes/Ammo_handler.lua index 7d04378..cec5139 100644 --- a/classes/Ammo_handler.lua +++ b/classes/Ammo_handler.lua @@ -1,56 +1,102 @@ Ammo_handler = Instantiatable_class:inherit({ - name = "Gun_ammo_handler", + name = "Ammo_handler", construct = function(def) - assert(def.gun) - def.itemstack = def.gun.itemstack - def.handler = def.gun.handler - def.inventory = def.handler.inventory - local meta = def.gun.meta - - - - if gun.properties.magazine then - local mag_meta = meta:get_string("guns4d_loaded_mag") - if mag_meta == "" then - meta:set_string("guns4d_loaded_mag", gun.properties.magazine.comes_with or "empty") - meta:set_string("guns4d_loaded_bullets", minetest.serialize({})) - else - def.mag = mag_meta - def.bullets = minetest.deserialize(meta:get_string("guns4d_loaded_bullets")) - end - else - local bullet_meta = meta:get_string("guns4d_loaded_bullets") - if bullet_meta == "" then - meta:set_string("guns4d_loaded_bullets", minetest.serialize({})) - else - def.ammo.bullets = minetest.deserailize(bullet_meta) + if def.instance then + assert(def.gun, "no gun") + def.itemstack = def.gun.itemstack + def.handler = def.gun.handler + def.inventory = def.handler.inventory + local meta = def.gun.meta + local gun = def.gun + def.ammo = {} + if gun.properties.magazine then + if meta:get_string("guns4d_loaded_bullets") == "" then + meta:set_string("guns4d_loaded_mag", gun.properties.magazine.comes_with or "empty") + meta:set_string("guns4d_next_bullet", "empty") + meta:set_int("guns4d_total_bullets", 0) + meta:set_string("guns4d_loaded_bullets", minetest.serialize({})) + def.ammo.loaded_mag = "empty" + def.ammo.next_bullet = "empty" + def.ammo.total_bullets = 0 + def.ammo.bullets = {} + else + def.ammo.loaded_mag = meta:get_string("guns4d_loaded_mag") + def.ammo.bullets = minetest.deserialize(meta:get_string("guns4d_loaded_bullets")) + def.ammo.total_bullets = meta:get_int("guns4d_total_bullets") + def.ammo.next_bullet = meta:get_string("guns4d_next_bullet") + end end end end }) -function Gun_ammo:load_mag() - local inv = self.inventory - for _, ammunition in pairs(self.gun.accepted_mags) do - for i = 1, inv:get_size("main") do +--spend the round, return false if impossible. +function Ammo_handler:spend_round() + local bullet_spent = self.ammo.next_bullet + local meta = self.gun.meta + --subtract the bullet + if self.ammo.total_bullets > 0 then + self.ammo.bullets[bullet_spent] = self.ammo.bullets[bullet_spent]-1 + if self.ammo.bullets[bullet_spent] == 0 then self.ammo.bullets[bullet_spent] = nil end + self.ammo.total_bullets = self.ammo.total_bullets - 1 + meta:set_string("guns4d_loaded_bullets", minetest.serialize(self.ammo.bullets)) + --set the new current bullet + if next(self.ammo.bullets) then + self.ammo.next_bullet = math.weighted_randoms(self.ammo.bullets) + meta:set_string("guns4d_next_bullet", self.ammo.next_bullet) + else + self.ammo.next_bullet = "empty" + meta:set_string("guns4d_next_bullet", "empty") + end + else + return false + end +end +function Ammo_handler:load_mag() + local inv = self.inventory + local magstack_index + local highest_ammo = 0 + local gun = self.gun + local gun_accepts = gun.accepted_ammo + for i, v in pairs(inv:get_list("main")) do + if gun_accepts[v:get_name()] then + local meta = v:get_meta() + if meta:get_int("guns4d_total_bullets") > highest_ammo then + magstack_index = i + end end end - if magstack then - ammo_table = minetest.deserialize(magstack:get_meta():get_string("ammo")) - inv:set_stack("main", index, "") - state = next_state - state_changed = true + if magstack_index then + local magstack = inv:get_stack("main", magstack_index) + local magstack_meta = magstack:get_meta() + --get the ammo stuff + local meta = self.gun.meta + + self.ammo.loaded_mag = magstack:get_name() + self.ammo.bullets = minetest.deserialize(magstack_meta:get_string("guns4d_loaded_bullets")) + self.ammo.total_bullets = magstack_meta:get_int("guns4d_total_bullets") + self.ammo.next_bullet = magstack_meta:get_string("guns4d_next_bullet") + -- + meta:set_string("guns4d_loaded_mag", self.ammo.loaded_mag) + meta:set_string("guns4d_loaded_bullets", magstack_meta:get_string("guns4d_loaded_bullets")) + meta:set_int("guns4d_total_bullets", self.ammo.total_bullets) + meta:set_string("guns4d_next_bullet", self.ammo.next_bullet) + + + inv:set_stack("main", magstack_index, "") + return end end -function Gun_ammo:unload_mag() + +function Ammo_handler:unload_mag() end -function Gun_ammo:load_magless() +function Ammo_handler:load_magless() end -function Gun_ammo:unload_magless() +function Ammo_handler:unload_magless() end -function Gun_ammo:load_fractional() +function Ammo_handler:load_fractional() end -function Gun_ammo:unload_fractional() +function Ammo_handler:unload_fractional() end -function Gun_ammo:unload_chamber() +function Ammo_handler:unload_chamber() end \ No newline at end of file diff --git a/classes/Gun.lua b/classes/Gun.lua index 5006375..97af586 100644 --- a/classes/Gun.lua +++ b/classes/Gun.lua @@ -53,11 +53,41 @@ local gun_default = { gun_axial = {x=.2, y=-.2}, player_axial = {x=1,y=1}, }, + controls = { + aim = { + conditions = {"RMB"}, + loop = false, + timer = 0, + func = function(active, interrupted, data, busy_list, handler) + if active then + handler.control_bools.ads = not handler.control_bools.ads + end + end + }, + fire = { + conditions = {"LMB"}, + loop = true, + timer = 0, + func = function(active, interrupted, data, busy_list, handler) + if not handler.control_handler.busy_list.on_use then + handler.gun:attempt_fire() + end + end + }, + on_use = function(itemstack, handler, pointed_thing) + handler.gun:attempt_fire() + handler.control_handler.busy_list.on_use = true + end + }, + magazine = { + magazine_only = false, + accepted_magazines = {} + }, + accepted_bullets = {}, flash_offset = Vec.new(), aim_time = 1, firerateRPM = 10000, - controls = {}, - accepted_mags = {} + ammo_handler = Ammo_handler }, offsets = { player_rotation = Vec.new(), @@ -115,9 +145,9 @@ local gun_default = { function gun_default:spend_round() end -function gun_default:fire() +function gun_default:attempt_fire() assert(self.instance, "attempt to call object method on a class") - if self.rechamber_time <= 0 then + if self.rechamber_time <= 0 and self.ammo_handler:spend_round() then local dir = self.dir local pos = self:get_pos() Guns4d.bullet_ray:new({ @@ -421,7 +451,14 @@ gun_default.construct = function(def) def.offsets[i] = Vec.new() end end - + def.accepted_bullets = {} + for i, v in pairs(def.properties.accepted_bullets) do + def.accepted_bullets[i] = true + end + def.accepted_magazines = {} + for i, v in pairs(def.properties.magazine.accepted_magazines) do + def.accepted_bullets[i] = true + end --def.velocities = table.deep_copy(def.base_class.velocities) def.velocities = {} @@ -440,7 +477,14 @@ gun_default.construct = function(def) }) end end + if def.properties.entity_scope then + if not def.sprite_scope then + end + end + def.ammo_handler = def.properties.ammo_handler:new({ + gun = def + }) elseif def.name ~= "__template__" then local props = def.properties assert(def.name, "no name provided") @@ -467,6 +511,7 @@ gun_default.construct = function(def) def.consts = table.fill(def.parent_class.consts, def.consts or {}) Guns4d.gun.registered[def.name] = def + --register the visual entity minetest.register_entity(def.name.."_visual", { initial_properties = { visual = "mesh", @@ -497,7 +542,6 @@ gun_default.construct = function(def) visibility = false end if handler.control_bools.ads then - local normal_pos = (props.ads.offset+Vec.new(props.ads.horizontal_offset,0,0))*10 obj:set_attach(player, lua_object.consts.AIMING_BONE, normal_pos, -axial_rot, visibility) else diff --git a/docs/gun properties.txt b/docs/gun properties.txt index e69de29..2c51f12 100644 --- a/docs/gun properties.txt +++ b/docs/gun properties.txt @@ -0,0 +1,43 @@ +{ + --defines the base recoil this gun experiences + recoil = { + --big ass table here + } + + + + + + + + +----------------------------------AMMUNITION-------------------------------------- + (praise the lord and pass the ammunition can't afford to be a politician) + + --note: internally clips are the same as a magazine (sort of) + + + --Ammo_handler derived class to handle ammunition, can be changed for modularity- not reccomended. + ammo_handler = Ammo_handler + + --a table containing info about the magazines this gun may use. + magazine = { + --define if the gun only uses a magazine + magazine_only = false, + + --the name of the magazine that the gun comes loaded with (optional) + comes_with = "modname:itemstring", + + --a list of magazines that this gun takes. These must be registered via Guns4d.ammo.register_magazine() + --note that if the magazine contains unaccepted bullets (properties.accepted_bullets), the magazine won't be accepted. + accepted_mags = { + "modname:itemstring", + "modname:itemstring" + } + } + + --list of bullets this gun accepts. These must be registered via Guns4d.ammo.register_bullet() + accepted_bullets = { + "modname:itemstring", + "modname:itemstring" + } diff --git a/docs/required_features.txt b/docs/required_features.txt index 8c5b2b7..11d5d25 100644 --- a/docs/required_features.txt +++ b/docs/required_features.txt @@ -40,4 +40,5 @@ auxillary (beta+/never) bullet tracers inverse kinematics stamina - a better system for reloading magazine (something like a inventory that fractionally reloads magazines so it takes time.) \ No newline at end of file + a better system for reloading magazine (something like a inventory that fractionally reloads magazines so it takes time.) + create a non-player gun API \ No newline at end of file diff --git a/gun_api.lua b/gun_api.lua index 07023e5..a62db6e 100644 --- a/gun_api.lua +++ b/gun_api.lua @@ -34,32 +34,6 @@ local default_def = { }, }, firerateRPM = 600, - controls = { - aim = { - conditions = {"RMB"}, - loop = false, - timer = 0, - func = function(active, interrupted, data, busy_list, handler) - if active then - handler.control_bools.ads = not handler.control_bools.ads - end - end - }, - fire = { - conditions = {"LMB"}, - loop = true, - timer = 0, - func = function(active, interrupted, data, busy_list, handler) - if not handler.control_handler.busy_list.on_use then - handler.gun:fire() - end - end - }, - on_use = function(itemstack, handler, pointed_thing) - handler.gun:fire() - handler.control_handler.busy_list.on_use = true - end - }, consts = { HIP_PLAYER_GUN_ROT_RATIO = .6 }, diff --git a/init.lua b/init.lua index 8de5678..fbc585e 100644 --- a/init.lua +++ b/init.lua @@ -12,6 +12,7 @@ path = path .. "/classes" dofile(path.."/Instantiatable_class.lua") dofile(path.."/Bullet_ray.lua") dofile(path.."/Control_handler.lua") +dofile(path.."/Ammo_handler.lua") dofile(path.."/Sprite_scope.lua") dofile(path.."/Gun.lua") dofile(path.."/Player_model_handler.lua") diff --git a/misc_helpers.lua b/misc_helpers.lua index e0a81fe..194598e 100644 --- a/misc_helpers.lua +++ b/misc_helpers.lua @@ -6,6 +6,7 @@ function math.clamp(val, lower, upper) if lower > upper then lower, upper = upper, lower end return math.max(lower, math.min(upper, val)) end +--I need to store this so there arent duplicates lol function Unique_id.generate() local genned_ids = Unique_id.generated local id = string.sub(tostring(math.random()), 3) @@ -15,12 +16,36 @@ function Unique_id.generate() genned_ids[id] = true return id end +function math.weighted_randoms(tbl) + local total_weight = 0 + local new_tbl = {} + for i, v in pairs(tbl) do + total_weight=total_weight+v + table.insert(new_tbl, {i, v}) + end + local ran = math.random()*total_weight + --[[the point of the new table is so we can have them + sorted in order of weight, so we can check if the random + fufills the lower values first.]] + table.sort(new_tbl, function(a, b) return a[2] > b[2] end) + local scaled_weight = 0 --[[so this is added to the weight so it's chances are proportional + to it's actual weight as opposed to being wether the lower values are picked- if you have + weight 19 and 20, 20 would have a 1/20th chance of being picked if we didn't do this]] + for i, v in pairs(tbl) do + if (v[2]+scaled_weight) > ran then + return v[1] + end + scaled_weight = scaled_weight + v[2] + end +end function math.rand_sign(b) b = b or .5 local int = 1 if math.random() > b then int=-1 end return int end +--weighted randoms + --for table vectors that aren't vector objects ---@diagnostic disable-next-line: lowercase-global function tolerance_check(a,b,tolerance) @@ -133,28 +158,6 @@ function table.fill(tbl, replacement, preserve_reference, indexed_tables) end return new_table end ---fill "holes" in the tables. -function table.fill_in(tbl, replacement, preserve_reference, indexed_tables) - if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing - local new_table = tbl - if not preserve_reference then - new_table = table.deep_copy(tbl) - end - for i, v in pairs(replacement) do - if new_table[i]==nil then - if type(v)=="table" then - new_table[i] = table.deep_copy(v) - else - new_table[i] = v - end - else - if (type(new_table[i]) == "table") and (type(v) == "table") then - table.fill_in(new_table[i], v, true, indexed_tables) - end - end - end - return new_table -end --for class based OOP, ensure values containing a table in btbl are tables in a_tbl- instantiate, but do not fill. function table.instantiate_struct(tbl, btbl, indexed_tables) if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing @@ -178,6 +181,8 @@ function table.shallow_copy(t) return new_table end +function weighted_randoms() +end --for the following code and functions only: --for license see the link on the next line. diff --git a/register_ammo.lua b/register_ammo.lua index 3f74417..0ca0ffa 100644 --- a/register_ammo.lua +++ b/register_ammo.lua @@ -11,6 +11,8 @@ Default_mag = { craft_reload = true } Guns4d.ammo = { + default_empty_loaded_bullets = { + }, registered_bullets = { }, @@ -20,7 +22,7 @@ Guns4d.ammo = { } local max_wear = 65535 function Guns4d.ammo.register_bullet(def) - assert(def.itemstring) + assert(def.itemstring, "no itemstring") assert(minetest.registered_items[def.itemstring], "no item '"..def.itemstring.."' found. Must be a registered item (check dependencies?)") Guns4d.ammo.registered_bullets[def.itemstring] = table.fill(Default_bullet, def) end @@ -32,16 +34,21 @@ function Guns4d.ammo.initialize_mag_data(itemstack, meta) end return itemstack end -function Guns4d.ammo.update_mag_wear(def, itemstack, meta) +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 itemstack:set_wear(max_wear-(max_wear*count/def.capacity)) + meta:set_int("guns4d_total_bullets", count) + meta:set_string("guns4d_next_bullet", current_bullet) return itemstack end + function Guns4d.ammo.register_magazine(def) def = table.fill(Default_mag, def) assert(def.accepted_bullets, "missing property def.accepted_bullets. Need specified bullets to allow for loading") @@ -60,7 +67,7 @@ function Guns4d.ammo.register_magazine(def) old_on_use(itemstack, user, pointed_thing) end local meta = itemstack:get_meta() - local ammo = minetest.deserialize(meta:get_string("guns4d_loaded_bullets")) + local ammo = meta:get_int("guns4d_total_bullets") if ammo then minetest.chat_send_player(user:get_player_name(), "rounds in magazine:") for i, v in pairs(ammo) do @@ -149,8 +156,9 @@ function Guns4d.ammo.register_magazine(def) end mag_stack:set_count(mag_stack:get_count()-1) craft_inv:set_stack("craft", mag_stack_index, mag_stack) - new_stack:get_meta():set_string("guns4d_loaded_bullets", minetest.serialize(new_ammo_table)) - new_stack = Guns4d.ammo.update_mag_wear(def, new_stack) + local meta = new_stack:get_meta() + meta:set_string("guns4d_loaded_bullets", minetest.serialize(new_ammo_table)) + new_stack = Guns4d.ammo.update_mag(def, new_stack, meta) --print(new_stack:get_string()) return new_stack end