guns4d-cd2025/classes/Ammo_handler.lua

223 lines
9.4 KiB
Lua

Ammo_handler = Instantiatable_class:inherit({
name = "Ammo_handler",
construct = function(def)
if def.instance then
assert(def.gun, "no gun")
def.handler = def.gun.handler
def.inventory = def.handler.inventory
local meta = def.gun.meta
local gun = def.gun
def.ammo = {}
if gun.properties.ammo then
if meta:get_string("guns4d_loaded_bullets") == "" then
def.ammo.loaded_mag = gun.properties.ammo.comes_with or "empty"
def.ammo.next_bullet = "empty"
def.ammo.total_bullets = 0
def.ammo.loaded_bullets = {}
def:update_meta()
else
def.ammo.loaded_mag = meta:get_string("guns4d_loaded_mag")
def.ammo.loaded_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
})
--spend the round, return false if impossible.
--updates all properties based on the ammo table, bullets string can be passed directly to avoid duplication (when needed)
function Ammo_handler:update_meta(bullets)
assert(self.instance, "attempt to call object method on a class")
local meta = self.gun.meta
meta:set_string("guns4d_loaded_mag", self.ammo.loaded_mag)
meta:set_string("guns4d_loaded_bullets", bullets or minetest.serialize(self.ammo.loaded_bullets))
meta:set_int("guns4d_total_bullets", self.ammo.total_bullets)
meta:set_string("guns4d_next_bullet", self.ammo.next_bullet)
if self.gun.ammo_handler then --if it's a first occourance it cannot work.
self.gun:update_image_and_text_meta(meta)
end
self.handler.player:set_wielded_item(self.gun.itemstack)
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")
local bullet_spent = self.ammo.next_bullet
local meta = self.gun.meta
--subtract the bullet
if self.ammo.total_bullets > 0 then
--only actually subtract the round if infinite_ammo is false.
if not self.handler.infinite_ammo then
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
end
--set the new current bullet
if next(self.ammo.loaded_bullets) then
self.ammo.next_bullet = math.weighted_randoms(self.ammo.loaded_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
self:update_meta()
return bullet_spent
end
end
function Ammo_handler:load_magazine()
assert(self.instance, "attempt to call object method on a class")
local inv = self.inventory
local magstack_index
local highest_ammo = -1
local gun = self.gun
local gun_accepts = gun.accepted_magazines
if self.ammo.loaded_mag ~= "empty" or self.ammo.total_bullets > 0 then
--it's undefined, make assumptions.
self:unload_all()
end
for i, v in pairs(inv:get_list("main")) do
if gun_accepts[v:get_name()] then
local meta = v:get_meta()
--intiialize data if it doesn't exist so it doesnt kill itself
if 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")
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
if not gun.accepted_bullets[bullet] then
has_unaccepted = true
break
end
end
if not has_unaccepted then magstack_index = i end
end
end
end
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
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 = magstack_meta:get_string("guns4d_next_bullet")
self:update_meta()
inv:set_stack("main", magstack_index, "")
return
end
end
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
return true
end
if (not gun.properties.magazine_only) and gun.accepted_bullets[v:get_name()] then
return true
end
end
return false
end
function Ammo_handler:can_load_magazine()
local inv = self.inventory
local gun = self.gun
local gun_accepts = gun.accepted_magazines
for i, v in pairs(inv:get_list("main")) do
if gun_accepts[v:get_name()] then
return true
end
end
return false
end
function Ammo_handler:unload_magazine(to_ground)
assert(self.instance, "attempt to call object method on a class")
if self.ammo.loaded_mag ~= "empty" then
local inv = self.handler.inventory
local magstack = ItemStack(self.ammo.loaded_mag)
local magmeta = magstack:get_meta()
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"))
magmeta:set_string("guns4d_next_bullet", gunmeta:get_string("guns4d_next_bullet"))
magstack = Guns4d.ammo.update_mag(nil, magstack, magmeta)
--throw it on the ground if to_ground is true
local remaining
if to_ground then
remaining = magstack
else
remaining = inv:add_item("main", magstack)
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}))
end
self.ammo.loaded_mag = "empty"
self.ammo.next_bullet = "empty"
self.ammo.total_bullets = 0
self.ammo.loaded_bullets = {}
self:update_meta()
end
end
--this is used for unloading flat, or unloading as a "clip" aka a feed only magazine, you'd use this for something like an m1 garand. God that ping.
function Ammo_handler:unload_all(to_ground)
assert(self.instance, "attempt to call object method on a class")
local inv = self.handler.inventory
for i, v in pairs(self.ammo.loaded_bullets) do
local leftover
--if to_ground is true throw it to the ground
if to_ground then
leftover = ItemStack("main", i.." "..tostring(v))
else
leftover = inv:add_item("main", i.." "..tostring(v))
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
local stack
if to_ground or Guns4d.ammo.registered_magazines[self.ammo.loaded_mag].hot_eject then
stack = ItemStack(self.ammo.loaded_mag)
else
stack = inv:add_item("main", self.ammo.loaded_mag)
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"
self.ammo.next_bullet = "empty"
self.ammo.total_bullets = 0
self.ammo.loaded_bullets = {}
self:update_meta()
end
function Ammo_handler:load_magless()
assert(self.instance, "attempt to call object method on a class")
end
function Ammo_handler:unload_magless()
assert(self.instance, "attempt to call object method on a class")
end
function Ammo_handler:load_fractional()
assert(self.instance, "attempt to call object method on a class")
end
function Ammo_handler:unload_fractional()
assert(self.instance, "attempt to call object method on a class")
end
function Ammo_handler:unload_chamber()
assert(self.instance, "attempt to call object method on a class")
end