added blunt, sharp penetration, fixed table fill-ins for property subtables, probably other various fixed
This commit is contained in:
parent
4230017960
commit
36e2af9f20
@ -23,14 +23,14 @@ minetest.register_on_mods_loaded(function()
|
||||
local random_deviation = 1
|
||||
local behavior_type = "normal"
|
||||
if groups.wood then
|
||||
RHA = RHA*groups.wood*.1
|
||||
RHA = RHA*.1
|
||||
random_deviation = random_deviation/groups.wood
|
||||
end
|
||||
if groups.oddly_breakable_by_hand then
|
||||
RHA = RHA / groups.oddly_breakable_by_hand
|
||||
end
|
||||
if groups.choppy then
|
||||
RHA = RHA*(1+(groups.choppy*.5))
|
||||
RHA = RHA*.5
|
||||
end
|
||||
if groups.flora or groups.grass then
|
||||
RHA = 0
|
||||
@ -42,12 +42,12 @@ minetest.register_on_mods_loaded(function()
|
||||
random_deviation = .005
|
||||
end
|
||||
if groups.stone then
|
||||
RHA = groups.stone
|
||||
RHA = 1/groups.stone
|
||||
random_deviation = .5
|
||||
end
|
||||
if groups.cracky then
|
||||
RHA = RHA*groups.cracky
|
||||
random_deviation = random_deviation*(groups.cracky*.5)
|
||||
RHA = RHA*(.5/groups.cracky)
|
||||
random_deviation = random_deviation*(.5/groups.cracky)
|
||||
end
|
||||
if groups.crumbly then
|
||||
RHA = RHA/groups.crumbly
|
||||
|
@ -3,84 +3,86 @@ Ammo_handler = Instantiatable_class:inherit({
|
||||
construct = function(def)
|
||||
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 gun.properties.ammo 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.loaded_mag = gun.properties.ammo.comes_with or "empty"
|
||||
def.ammo.next_bullet = "empty"
|
||||
def.ammo.total_bullets = 0
|
||||
def.ammo.bullets = {}
|
||||
def.ammo.loaded_bullets = {}
|
||||
def:update_meta()
|
||||
else
|
||||
def.ammo.loaded_mag = meta:get_string("guns4d_loaded_mag")
|
||||
def.ammo.bullets = minetest.deserialize(meta:get_string("guns4d_loaded_bullets"))
|
||||
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")
|
||||
def:update_has_ammo()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
--spend the round, return false if impossible.
|
||||
function Ammo_handler:update_has_ammo()
|
||||
--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")
|
||||
if next(self.ammo.bullets) then
|
||||
self.has_ammo = true
|
||||
else
|
||||
self.has_ammo = true
|
||||
end
|
||||
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)
|
||||
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
|
||||
print(bullet_spent)
|
||||
print(self.ammo.total_bullets)
|
||||
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.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
|
||||
meta:set_string("guns4d_loaded_bullets", minetest.serialize(self.ammo.bullets))
|
||||
meta:set_int("guns4d_total_bullets", self.ammo.total_bullets)
|
||||
--set the new current bullet
|
||||
if next(self.ammo.bullets) then
|
||||
self.ammo.next_bullet = math.weighted_randoms(self.ammo.bullets)
|
||||
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
|
||||
minetest.chat_send_all(self.ammo.total_bullets)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
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 = 0
|
||||
local highest_ammo = -1
|
||||
local gun = self.gun
|
||||
local gun_accepts = gun.accepted_magazines
|
||||
print(dump(gun_accepts))
|
||||
if self.ammo.loaded_mag ~= "empty" 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
|
||||
print("success1")
|
||||
local meta = v:get_meta()
|
||||
if meta:get_int("guns4d_total_bullets") > highest_ammo then
|
||||
print("success2")
|
||||
--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
|
||||
print(meta:get_string("guns4d_loaded_bullets"))
|
||||
for bullet, _ in pairs(minetest.deserialize(meta:get_string("guns4d_loaded_bullets"))) do
|
||||
if not gun.accepted_bullets[bullet] then
|
||||
has_unaccepted = true
|
||||
@ -97,24 +99,108 @@ function Ammo_handler:load_magazine()
|
||||
--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.bullets = minetest.deserialize(magstack_meta:get_string("guns4d_loaded_bullets"))
|
||||
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")
|
||||
--
|
||||
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)
|
||||
|
||||
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_mag()
|
||||
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
|
||||
minetest.chat_send_all("not empty")
|
||||
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")
|
||||
|
@ -7,7 +7,7 @@ local ray = {
|
||||
--last_dir
|
||||
--exit_direction = dir,
|
||||
--range_left = def.bullet.range,
|
||||
--force_mmRHA = def.bullet.penetration_RHA
|
||||
--energy = def.bullet.penetration_RHA
|
||||
ITERATION_DISTANCE = .3,
|
||||
damage = 0
|
||||
}
|
||||
@ -19,7 +19,7 @@ function ray:record_state()
|
||||
})
|
||||
end
|
||||
--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
|
||||
function ray:transverse_end_point()
|
||||
function ray:find_transverse_end_point()
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local pointed
|
||||
local cast = minetest.raycast(self.pos+(self.dir*(self.ITERATION_DISTANCE+.01)), self.pos, false, false)
|
||||
@ -31,44 +31,50 @@ function ray:transverse_end_point()
|
||||
end
|
||||
end
|
||||
if pointed and vector.distance(pointed.intersection_point, self.pos) < self.ITERATION_DISTANCE then
|
||||
self.normal = pointed.intersection_normal
|
||||
self.exit_direction = vector.direction(self.dir, vector.new()) --reverse dir is exit direction (for VFX)
|
||||
return pointed.intersection_point
|
||||
return pointed.intersection_point, pointed.intersection_normal
|
||||
end
|
||||
end
|
||||
function ray:cast()
|
||||
function ray:_cast()
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local end_pos = self.pos+(self.dir*self.range)
|
||||
--if block ends early, then we set end position accordingly
|
||||
local next_penetration_val
|
||||
local next_state = self.state --next state of course the state of the next ray.
|
||||
|
||||
local end_normal
|
||||
|
||||
local end_pos
|
||||
local edge
|
||||
local next_state = self.state
|
||||
--if block ends early, then we find it and set end position of the ray accordingly.
|
||||
--edge is where the section of solid blocks ends and becomes open air again.
|
||||
if self.state == "transverse" then
|
||||
edge = self:transverse_end_point()
|
||||
edge, end_normal = self:find_transverse_end_point()
|
||||
if edge then
|
||||
end_pos = edge
|
||||
next_state = "free"
|
||||
else
|
||||
end_pos = self.pos+(self.dir*self.ITERATION_DISTANCE)
|
||||
end
|
||||
else
|
||||
end_pos = self.pos+(self.dir*self.range)
|
||||
end
|
||||
local continue = true
|
||||
--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 cast = minetest.raycast(self.pos, end_pos, true, true)
|
||||
local pointed
|
||||
for hit in cast do
|
||||
if not continue then break end
|
||||
if vector.distance(hit.intersection_point, self.pos) > 0.0005 and vector.distance(hit.intersection_point, self.pos) < self.range then
|
||||
--if it's a node, check that it's note supposed to be ignored according to it's generated properties
|
||||
if hit.type == "node" then
|
||||
if self.state == "free" and Guns4d.node_properties[minetest.get_node(hit.under).name].behavior ~= "ignore" then
|
||||
next_state = "transverse"
|
||||
pointed = hit
|
||||
end_normal = hit.intersection_normal
|
||||
end_pos = pointed.intersection_point
|
||||
break
|
||||
end
|
||||
if self.state == "transverse" then
|
||||
--if it isn't the same name as the last node we intersected, then it's a different block with different stats for penetration
|
||||
if minetest.get_node(hit.under).name ~= self.last_node_name then
|
||||
pointed = hit
|
||||
end_pos = pointed.intersection_point
|
||||
end
|
||||
--make sure it's set to transverse if the edge has a block infront of it
|
||||
if Guns4d.node_properties[minetest.get_node(hit.under).name].behavior == "ignore" then
|
||||
@ -81,7 +87,7 @@ function ray:cast()
|
||||
end
|
||||
--if it's an object, make sure it's not the player object
|
||||
--note that while it may seem like this will create a infinite hit loop, it resolves itself as the intersection_point of the next ray will be close enough as to skip the pointed. See first line of iterator.
|
||||
if hit.type == "object" and hit.ref ~= self.player then
|
||||
if (hit.type == "object") and (hit.ref ~= self.player) and ((not self.last_pointed) or (hit.ref ~= self.last_pointed.ref)) then
|
||||
if self.over_penetrate then
|
||||
pointed = hit
|
||||
break
|
||||
@ -90,62 +96,83 @@ function ray:cast()
|
||||
continue = false
|
||||
break
|
||||
end
|
||||
end_pos = pointed.intersection_point
|
||||
end
|
||||
end
|
||||
end
|
||||
if pointed then
|
||||
--[[if pointed then
|
||||
end_pos = pointed.intersection_point
|
||||
if self.state == "transverse" then
|
||||
next_penetration_val = self.force_mmRHA-(vector.distance(self.pos, end_pos)*Guns4d.node_properties[self.last_node_name].mmRHA)
|
||||
next_penetration_val = self.energy-(vector.distance(self.pos, end_pos)*Guns4d.node_properties[self.last_node_name].mmRHA)
|
||||
else -- transverse
|
||||
next_penetration_val = self.force_mmRHA-(vector.distance(self.pos, end_pos)*self.dropoff_mmRHA)
|
||||
next_penetration_val = self.energy-(vector.distance(self.pos, end_pos)*self.dropoff_mmRHA)
|
||||
end
|
||||
else
|
||||
--if there is no pointed, and it's not transverse, then the ray has ended.
|
||||
if self.state == "transverse" then
|
||||
next_penetration_val = self.force_mmRHA-(vector.distance(self.pos, end_pos)*Guns4d.node_properties[self.last_node_name].mmRHA)
|
||||
next_penetration_val = self.energy-(vector.distance(self.pos, end_pos)*Guns4d.node_properties[self.last_node_name].mmRHA)
|
||||
else --free
|
||||
continue = false
|
||||
next_penetration_val = self.force_mmRHA-(self.range*self.dropoff_mmRHA)
|
||||
next_penetration_val = self.energy-(self.range*self.dropoff_mmRHA)
|
||||
end
|
||||
end
|
||||
end]]
|
||||
|
||||
--set "last" values.
|
||||
return pointed, next_penetration_val, next_state, end_pos, continue
|
||||
return pointed, next_state, end_pos, end_normal, continue
|
||||
end
|
||||
function ray:iterate(initialized)
|
||||
--the main function.
|
||||
function ray:_iterate(initialized)
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local pointed, penetration, next_state, end_pos, continue = self:cast()
|
||||
self.range = self.range-vector.distance(self.pos, end_pos)
|
||||
self.pos = end_pos
|
||||
self.force_mmRHA = penetration
|
||||
local pointed, next_state, end_pos, end_normal, continue = self:_cast()
|
||||
|
||||
local distance = vector.distance(self.pos, end_pos)
|
||||
if self.state == "free" then
|
||||
self.energy = self.energy-(distance*self.energy_dropoff)
|
||||
else
|
||||
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.
|
||||
minetest.chat_send_all(penetration_loss/self.init_penetration)
|
||||
minetest.chat_send_all(distance)
|
||||
minetest.chat_send_all(Guns4d.node_properties[self.last_node_name].mmRHA)
|
||||
--minetest.chat_send_all(penetration_loss)
|
||||
self.energy = self.energy-((self.init_energy*self.energy_sharp_ratio)*(penetration_loss/self.init_penetration))
|
||||
end
|
||||
--set values for next iteration.
|
||||
self.range = self.range-distance
|
||||
if self.range <= 0.0005 or self.energy < 0 then
|
||||
continue = false
|
||||
minetest.chat_send_all("range ended, dist:"); minetest.chat_send_all(tostring(distance))
|
||||
end
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
self.state = next_state
|
||||
if pointed then
|
||||
self.last_pointed = pointed
|
||||
end
|
||||
if pointed then
|
||||
if pointed.type == "node" then
|
||||
self.last_node_name = minetest.get_node(pointed.under).name
|
||||
elseif pointed.type == "object" then
|
||||
ray:hit_entity(pointed.ref)
|
||||
self.pos = pointed.intersection_point
|
||||
if self.energy > 0 then
|
||||
if pointed.type == "node" then
|
||||
self.last_node_name = minetest.get_node(pointed.under).name
|
||||
elseif pointed.type == "object" then
|
||||
ray:hit_entity(pointed.ref)
|
||||
end
|
||||
end
|
||||
else
|
||||
self.pos = end_pos
|
||||
end
|
||||
table.insert(self.history, {
|
||||
pos = self.pos,
|
||||
force_mmRHA = self.force_mmRHA,
|
||||
energy = self.energy,
|
||||
state = self.state,
|
||||
last_node = self.last_node_name,
|
||||
normal = self.normal,
|
||||
normal = end_normal, --end normal may be nil, as it's only for hit effects.
|
||||
})
|
||||
if continue and self.range > 0 and self.force_mmRHA > 0 then
|
||||
self:iterate(true)
|
||||
if continue and self.range > 0 and self.energy > 0 then
|
||||
self:_iterate(true)
|
||||
end
|
||||
if not initialized then
|
||||
for i, v in pairs(self.history) do
|
||||
--[[local hud = self.player:hud_add({
|
||||
local hud = self.player:hud_add({
|
||||
hud_elem_type = "waypoint",
|
||||
text = "mmRHA:"..tostring(math.floor(v.force_mmRHA or 0)).." ",
|
||||
text = "mmRHA:"..tostring(v.energy).." ",
|
||||
number = 255255255,
|
||||
precision = 1,
|
||||
world_pos = v.pos,
|
||||
@ -155,26 +182,49 @@ function ray:iterate(initialized)
|
||||
})
|
||||
minetest.after(40, function(hud)
|
||||
self.player:hud_remove(hud)
|
||||
end, hud)]]
|
||||
end, hud)
|
||||
end
|
||||
end
|
||||
end
|
||||
function ray:calculate_blunt_damage(bullet, armor, groups)
|
||||
end
|
||||
function ray:calculate_sharp_conversion(bullet, armor, groups)
|
||||
end
|
||||
function ray:calculate_sharp_damage(bullet, armor, groups)
|
||||
end
|
||||
function ray:apply_damage(object, blunt_pen, sharp_pen, blunt_dmg, sharp_dmg)
|
||||
object:punch()
|
||||
end
|
||||
function ray.construct(def)
|
||||
if def.instance then
|
||||
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.force_mmRHA, "no force")
|
||||
assert(def.dropoff_mmRHA, "no force dropoff")
|
||||
--assert(def.on_node_hit, "no node hit behavior")
|
||||
assert(def.hit_entity, "no entity hit behavior")
|
||||
def.init_force_mmRHA = def.force_mmRHA
|
||||
assert(def.energy, "no energy")
|
||||
assert(def.energy_dropoff, "no energy dropoff")
|
||||
assert(def.blunt_damage, "no blunt damage")
|
||||
|
||||
def.sharp_damage = def.sharp_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.init_energy = def.energy
|
||||
def.init_penetration = def.sharp_penetration
|
||||
def.init_blunt = def.blunt_penetration
|
||||
--blunt pen is in the same units (1 Joule/Area^3 = 1 MPa), so we use it to make the ratio by subtraction.
|
||||
def.energy_sharp_ratio = (def.energy-def.blunt_penetration)/def.energy
|
||||
|
||||
def.dir = vector.new(def.dir)
|
||||
def.pos = vector.new(def.pos)
|
||||
def.history = {}
|
||||
def:iterate()
|
||||
def:_iterate()
|
||||
end
|
||||
end
|
||||
Guns4d.bullet_ray = Instantiatable_class:inherit(ray)
|
@ -10,13 +10,17 @@ Guns4d.control_handler = {
|
||||
call_before_timer = false,
|
||||
loop = false,
|
||||
func=function(active, interrupted, data, busy_controls)
|
||||
data = {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
]]
|
||||
}
|
||||
--data table:
|
||||
--[[
|
||||
{
|
||||
held = bool
|
||||
timer = float
|
||||
}
|
||||
]]
|
||||
local controls = Guns4d.control_handler
|
||||
--[[-modify controls (future implementation if needed)
|
||||
function controls.modify()
|
||||
@ -46,7 +50,7 @@ function controls:update(dt)
|
||||
if data.timer <= 0 and ((not data.held) or def.loop) then
|
||||
data.held = true
|
||||
table.insert(call_queue, {control=def, active=true, interrupt=false, data=data})
|
||||
elseif def.call_before_timer then --this is useful for functionst that need to play animations for their progress.
|
||||
elseif def.call_before_timer and not data.held 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
|
||||
@ -60,7 +64,6 @@ function controls:update(dt)
|
||||
end
|
||||
end
|
||||
end
|
||||
local count = 0
|
||||
--busy list is so we can tell if a function should be allowed or not
|
||||
if #busy_list == 0 then busy_list = nil end
|
||||
for i, tbl in pairs(call_queue) do
|
||||
|
317
classes/Gun.lua
317
classes/Gun.lua
@ -2,19 +2,21 @@ local Vec = vector
|
||||
local gun_default = {
|
||||
--itemstack = Itemstack
|
||||
--gun_entity = ObjRef
|
||||
name = "__template__",
|
||||
name = "__guns4d:default__",
|
||||
itemstring = "",
|
||||
registered = {},
|
||||
property_modifiers = {},
|
||||
properties = {
|
||||
hip = {
|
||||
hip = { --used by gun entity (attached offset)
|
||||
offset = Vec.new(),
|
||||
},
|
||||
ads = {
|
||||
ads = { --used by player_handler, animation handler (eye bone offset from horizontal_offset), gun entity (attached offset)
|
||||
offset = Vec.new(),
|
||||
horizontal_offset = 0,
|
||||
aim_time = 1,
|
||||
},
|
||||
recoil = {
|
||||
velocity_correction_factor = {
|
||||
recoil = { --used by update_recoil()
|
||||
velocity_correction_factor = { --velocity correction factor is currently very broken.
|
||||
gun_axial = 1,
|
||||
player_axial = 1,
|
||||
},
|
||||
@ -22,7 +24,7 @@ local gun_default = {
|
||||
gun_axial = 1,
|
||||
player_axial = 1,
|
||||
},
|
||||
angular_velocity_max = {
|
||||
angular_velocity_max = { --max velocity, so your gun doesnt "spin me right round baby round round"
|
||||
gun_axial = 1,
|
||||
player_axial = 1,
|
||||
},
|
||||
@ -39,7 +41,7 @@ local gun_default = {
|
||||
player_axial = 1,
|
||||
},
|
||||
},
|
||||
sway = {
|
||||
sway = { --used by update_sway()
|
||||
max_angle = {
|
||||
gun_axial = 0,
|
||||
player_axial = 0,
|
||||
@ -49,59 +51,34 @@ local gun_default = {
|
||||
player_axial = 0,
|
||||
},
|
||||
},
|
||||
walking_offset = {
|
||||
gun_axial = {x=.2, y=-.2},
|
||||
walking_offset = { --used by update_walking() (or something)
|
||||
gun_axial = {x=1, y=-1},
|
||||
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
|
||||
},
|
||||
reload = {
|
||||
conditions = {"zoom"},
|
||||
loop = false,
|
||||
timer = 0,
|
||||
func = function(active, interrupted, data, busy_list, handler)
|
||||
if not handler.control_handler.busy_list.on_use then
|
||||
local props = handler.gun.properties
|
||||
handler.gun.ammo_handler:load_magazine()
|
||||
if not props.magazine.magazine_only then
|
||||
--flat reload?
|
||||
end
|
||||
handler.player:set_wielded_item(handler.gun.itemstack)
|
||||
end
|
||||
end
|
||||
},
|
||||
on_use = function(itemstack, handler, pointed_thing)
|
||||
handler.gun:attempt_fire()
|
||||
handler.control_handler.busy_list.on_use = true
|
||||
end
|
||||
controls = { --used by control_handler
|
||||
__overfill=true, --if present, this table will not be filled in.
|
||||
aim = Guns4d.default_controls.aim,
|
||||
--fire = Guns4d.default_controls.fire,
|
||||
reload = Guns4d.default_controls.reload,
|
||||
on_use = Guns4d.default_controls.on_use
|
||||
},
|
||||
magazine = {
|
||||
reload = { --used by defualt controls. Still provides usefulness elsewhere.
|
||||
__overfill=true, --if present, this table will not be filled in.
|
||||
{type="unload", time=1, anim="unload", interupt="to_ground", hold = true},
|
||||
{type="load", time=1, anim="load"}
|
||||
},
|
||||
ammo = { --used by ammo_handler
|
||||
magazine_only = false,
|
||||
accepted_bullets = {},
|
||||
accepted_magazines = {}
|
||||
},
|
||||
accepted_bullets = {},
|
||||
flash_offset = Vec.new(),
|
||||
aim_time = 1,
|
||||
firerateRPM = 10000,
|
||||
animations = { --used by animations handler for idle, and default controls
|
||||
empty = {x=0,y=0},
|
||||
loaded = {x=1,y=1},
|
||||
},
|
||||
--used by ammo_handler
|
||||
flash_offset = Vec.new(), --used by fire() (for fsx and ray start pos) [RENAME NEEDED]
|
||||
firerateRPM = 600, --used by update() and by extent fire() + default controls
|
||||
ammo_handler = Ammo_handler
|
||||
},
|
||||
offsets = {
|
||||
@ -149,6 +126,15 @@ local gun_default = {
|
||||
HAS_SWAY = true,
|
||||
HAS_WAG = true,
|
||||
INFINITE_AMMO_IN_CREATIVE = true,
|
||||
DEFAULT_FPS = 20,
|
||||
LOOP_IDLE_ANIM = false
|
||||
},
|
||||
animation_data = { --where animations data is stored.
|
||||
anim_runtime = 0,
|
||||
length = 0,
|
||||
fps = 0,
|
||||
animations_frames = {0,0},
|
||||
current_frame = 0,
|
||||
},
|
||||
particle_spawners = {},
|
||||
walking_tick = 0,
|
||||
@ -158,32 +144,27 @@ local gun_default = {
|
||||
muzzle_flash = Guns4d.muzzle_flash
|
||||
}
|
||||
|
||||
function gun_default:spend_round()
|
||||
end
|
||||
function gun_default:attempt_fire()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if self.rechamber_time <= 0 then
|
||||
if self.ammo_handler:spend_round() then
|
||||
local spent_bullet = self.ammo_handler:spend_round()
|
||||
if spent_bullet then
|
||||
local dir = self.dir
|
||||
local pos = self:get_pos()
|
||||
Guns4d.bullet_ray:new({
|
||||
--[[print(dump(Guns4d.ammo.registered_bullets))
|
||||
print(self.ammo_handler.next_bullet)
|
||||
print(Guns4d.ammo.registered_bullets[self.ammo_handler.next_bullet])]]
|
||||
local bullet_def = table.fill(Guns4d.ammo.registered_bullets[spent_bullet], {
|
||||
player = self.player,
|
||||
pos = pos,
|
||||
dir = dir,
|
||||
range = 100,
|
||||
gun = self,
|
||||
force_mmRHA = 1,
|
||||
dropoff_mmRHA = 0,
|
||||
hit_entity = function(pointed)
|
||||
local damage = math.floor((self.damage*(self.force_mmRHA/self.init_force_mmRHA))+1)
|
||||
pointed.ref:punch(self.player, nil, {damage_groups = {fleshy = damage, penetration_mmRHA=self.force_mmRHA}}, self.dir)
|
||||
end
|
||||
gun = self
|
||||
})
|
||||
Guns4d.bullet_ray:new(bullet_def)
|
||||
self:recoil()
|
||||
self:muzzle_flash()
|
||||
self.rechamber_time = 60/self.properties.firerateRPM
|
||||
end
|
||||
self.player:set_wielded_item(self.itemstack)
|
||||
end
|
||||
end
|
||||
|
||||
@ -207,12 +188,12 @@ end
|
||||
function gun_default:get_player_axial_dir(rltv)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local player = self.player
|
||||
local player_rotation = 0
|
||||
if not rltv then player_rotation = self.offsets.player_rotation.x end
|
||||
local rotation = self.offsets.total_offset_rotation
|
||||
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.player_axial.x)*math.pi/180), z=0}))
|
||||
dir = Vec.rotate(dir, {y=((rotation.player_axial.y)*math.pi/180), x=0, z=0})
|
||||
dir = Vec.rotate(dir, {x=player_rotation*(math.pi/180),y=0,z=0})
|
||||
if not rltv then
|
||||
dir = Vec.rotate(dir, {x=self.offsets.player_rotation.x*(math.pi/180),y=0,z=0})
|
||||
end
|
||||
--[[local hud_pos = Vec.rotate(dir, {x=0,y=self.offsets.player_rotation.y*math.pi/180,z=0})+player:get_pos()+{x=0,y=player:get_properties().eye_height,z=0}+vector.rotate(player:get_eye_offset()/10, {x=0,y=self.offsets.player_rotation.y*math.pi/180,z=0})
|
||||
local hud = player:hud_add({
|
||||
hud_elem_type = "image_waypoint",
|
||||
@ -229,18 +210,13 @@ function gun_default:get_player_axial_dir(rltv)
|
||||
end
|
||||
function gun_default:get_dir(rltv)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local player = self.player
|
||||
local player_rotation
|
||||
if rltv then
|
||||
player_rotation = Vec.new()
|
||||
else
|
||||
player_rotation = Vec.new(self.offsets.player_rotation.x, self.offsets.player_rotation.y, 0)
|
||||
end
|
||||
local rotation = self.offsets.total_offset_rotation
|
||||
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})
|
||||
--for it to be relative the the camera, rotation by player look occours post.
|
||||
dir = Vec.rotate(dir, {x=player_rotation.x*math.pi/180,y=player_rotation.y*math.pi/180,z=0})
|
||||
if not rltv then
|
||||
dir = Vec.rotate(dir, {x=self.offsets.player_rotation.x*math.pi/180,y=self.offsets.player_rotation.y*math.pi/180,z=0})
|
||||
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",
|
||||
@ -253,7 +229,6 @@ function gun_default:get_dir(rltv)
|
||||
minetest.after(0, function(hud)
|
||||
player:hud_remove(hud)
|
||||
end, hud)]]
|
||||
|
||||
return dir
|
||||
end
|
||||
|
||||
@ -321,14 +296,18 @@ function gun_default:update(dt)
|
||||
local look_rotation = {x=handler.look_rotation.x,y=handler.look_rotation.y}
|
||||
local total_rot = self.offsets.total_offset_rotation
|
||||
local player_rot = self.offsets.player_rotation
|
||||
local constant = 1.4
|
||||
local constant = 6
|
||||
|
||||
--player look rotation
|
||||
local next_vert_aim = ((player_rot.x+look_rotation.x)/(1+((constant*10)*dt)))-look_rotation.x
|
||||
if math.abs(look_rotation.x-next_vert_aim) > .005 then
|
||||
player_rot.x = next_vert_aim
|
||||
--player look rotation. I'm going to keep it real, I don't remember what this equation does.
|
||||
if not self.sprite_scope then
|
||||
local next_vert_aim = ((player_rot.x+look_rotation.x)/(1+constant*dt))-look_rotation.x
|
||||
if math.abs(look_rotation.x-next_vert_aim) > .005 then
|
||||
player_rot.x = next_vert_aim
|
||||
else
|
||||
player_rot.x = -look_rotation.x
|
||||
end
|
||||
else
|
||||
player_rot.x = look_rotation.x
|
||||
player_rot.x = -look_rotation.x
|
||||
end
|
||||
--timers
|
||||
if self.rechamber_time > 0 then
|
||||
@ -346,6 +325,8 @@ function gun_default:update(dt)
|
||||
if self.consts.HAS_WAG then self:update_wag(dt) end
|
||||
self.dir = self:get_dir()
|
||||
self.local_dir = self:get_dir(true)
|
||||
self.paxial_dir = self:get_player_axial_dir()
|
||||
self.local_paxial_dir = self:get_player_axial_dir(true)
|
||||
|
||||
--sprite scope
|
||||
if self.properties.sprite_scope then
|
||||
@ -427,7 +408,66 @@ function gun_default:update_recoil(dt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local animation_data = {
|
||||
anim_runtime = 0,
|
||||
length = 0,
|
||||
fps = 0,
|
||||
animations_frames = {0,0},
|
||||
current_frame = 0,
|
||||
}
|
||||
function gun_default:animation_update(dt)
|
||||
local ent = self.entity
|
||||
local data = self.animation_data
|
||||
local anim_range, frame_speed, frame_blend, frame_loop = ent:get_animation()
|
||||
if (not data.animation_frames) or (anim_range.x ~= data.x) or (anim_range.y ~= data.y) then
|
||||
data.runtime = 0
|
||||
data.animations_frames = false
|
||||
elseif data.animation_frames then
|
||||
data.runtime = data.runtime + dt
|
||||
data.current_frame = math.clamp(data.runtime*data.fps, data.animation_frames.x, data.animation_frames.y)
|
||||
end
|
||||
end
|
||||
function gun_default:set_animation(frames, length, fps, loop)
|
||||
loop = loop or false --why the fuck default is loop? I DONT FUCKIN KNOW
|
||||
assert(type(frames)=="table" and frames.x and frames.y, "frames invalid or nil in set_animation()!")
|
||||
assert(length or fps, "need either length or FPS for animation")
|
||||
assert(not (length and fps), "cannot play animation with both specified length and specified fps. Only one parameter can be used.")
|
||||
local num_frames = math.abs(frames.x-frames.y)
|
||||
local data = self.animation_data
|
||||
if length then
|
||||
fps = num_frames/length
|
||||
elseif fps then
|
||||
length = num_frames/fps
|
||||
else
|
||||
fps = self.consts.DEFAULT_FPS
|
||||
length = num_frames/self.consts.DEFAULT_FPS
|
||||
end
|
||||
data.runtime = 0
|
||||
data.length = length
|
||||
self.entity:set_animation(frames, fps, 0, loop)
|
||||
end
|
||||
function gun_default:clear_animation()
|
||||
local loaded = false
|
||||
for i, v in pairs(self.ammo_handler) do
|
||||
print(i,v )
|
||||
end
|
||||
if self.properties.ammo.magazine_only then
|
||||
if self.ammo_handler.ammo.loaded_mag ~= "empty" then
|
||||
loaded = true
|
||||
end
|
||||
elseif self.ammo_handler.ammo.total_bullets > 0 then
|
||||
loaded = true
|
||||
end
|
||||
if loaded then
|
||||
self.entity:set_animation({x=self.properties.animations.loaded.x, y=self.properties.animations.loaded.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
|
||||
else
|
||||
self.entity:set_animation({x=self.properties.animations.empty.x, y=self.properties.animations.empty.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
|
||||
end
|
||||
local data = self.animation_data
|
||||
data.runtime = 0
|
||||
data.length = false
|
||||
data.animations_frames = false
|
||||
end
|
||||
function gun_default:update_breathing(dt)
|
||||
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-
|
||||
@ -469,6 +509,20 @@ function gun_default:prepare_deletion()
|
||||
if self.sprite_scope then self.sprite_scope:prepare_deletion() end
|
||||
end
|
||||
--construction for the base gun class
|
||||
local valid_ctrls = {
|
||||
up=true,
|
||||
down=true,
|
||||
left=true,
|
||||
right=true,
|
||||
jump=true,
|
||||
aux1=true,
|
||||
sneak=true,
|
||||
dig=true,
|
||||
place=true,
|
||||
LMB=true,
|
||||
RMB=true,
|
||||
zoom=true,
|
||||
}
|
||||
gun_default.construct = function(def)
|
||||
if def.instance then
|
||||
--make some quick checks.
|
||||
@ -513,15 +567,6 @@ gun_default.construct = function(def)
|
||||
def.offsets[i] = Vec.new()
|
||||
end
|
||||
end
|
||||
def.accepted_bullets = {}
|
||||
for _, v in pairs(def.properties.accepted_bullets) do
|
||||
def.accepted_bullets[v] = true
|
||||
end
|
||||
def.accepted_magazines = {}
|
||||
print(dump(def.properties.magazine.accepted_magazines))
|
||||
for _, v in pairs(def.properties.magazine.accepted_magazines) do
|
||||
def.accepted_magazines[v] = true
|
||||
end
|
||||
|
||||
--def.velocities = table.deep_copy(def.base_class.velocities)
|
||||
def.velocities = {}
|
||||
@ -541,39 +586,73 @@ gun_default.construct = function(def)
|
||||
end
|
||||
end
|
||||
if def.properties.entity_scope then
|
||||
if not def.sprite_scope then
|
||||
if not def.entity_scope then
|
||||
|
||||
end
|
||||
end
|
||||
def.ammo_handler = def.properties.ammo_handler:new({
|
||||
gun = def
|
||||
})
|
||||
elseif def.name ~= "__template__" then
|
||||
elseif def.name ~= "__guns4d:default__" then
|
||||
local props = def.properties
|
||||
assert(def.name, "no name provided")
|
||||
assert(def.itemstring, "no itemstring provided")
|
||||
assert(minetest.registered_items[def.itemstring], "item is not registered, check dependencies.")
|
||||
--override methods so control handler can do it's job
|
||||
local old_on_use = minetest.registered_items[def.itemstring].on_use
|
||||
local old_on_s_use = minetest.registered_items[def.itemstring].on_secondary_use
|
||||
minetest.override_item(def.itemstring, {
|
||||
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()].handler.control_handler:on_use(itemstack, pointed_thing)
|
||||
end,
|
||||
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()].handler.control_handler:on_secondary_use(itemstack, pointed_thing)
|
||||
end
|
||||
})
|
||||
def.properties = table.fill(def.parent_class.properties, def.properties or {})
|
||||
def.consts = table.fill(def.parent_class.consts, def.consts or {})
|
||||
|
||||
--validate controls, done before properties are filled to avoid duplication.
|
||||
if props.controls then
|
||||
for i, control in pairs(props.controls) 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
|
||||
if not valid_ctrls[condition] then
|
||||
assert(false, "invalid key: '"..condition.."'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--fill in the properties.
|
||||
def.properties = table.fill(def.parent_class.properties, props or {})
|
||||
print(table.tostring(def.properties))
|
||||
def.consts = table.fill(def.parent_class.consts, def.consts or {})
|
||||
props = def.properties --have to reinitialize this as the reference is replaced.
|
||||
|
||||
if def.name ~= "__template" then
|
||||
assert(rawget(def, "name"), "no name provided in new class")
|
||||
assert(rawget(def, "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(minetest.registered_items[def.itemstring], def.itemstring.." : item is not registered, check dependencies.")
|
||||
|
||||
--override methods so control handler can do it's job
|
||||
local old_on_use = minetest.registered_items[def.itemstring].on_use
|
||||
local old_on_s_use = minetest.registered_items[def.itemstring].on_secondary_use
|
||||
--override the item to hook in controls. (on_drop needed)
|
||||
minetest.override_item(def.itemstring, {
|
||||
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()].handler.control_handler:on_use(itemstack, pointed_thing)
|
||||
end,
|
||||
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()].handler.control_handler:on_secondary_use(itemstack, pointed_thing)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
def.accepted_bullets = {}
|
||||
for _, v in pairs(def.properties.ammo.accepted_bullets) do
|
||||
def.accepted_bullets[v] = true
|
||||
end
|
||||
def.accepted_magazines = {}
|
||||
for _, v in pairs(def.properties.ammo.accepted_magazines) do
|
||||
def.accepted_magazines[v] = true
|
||||
end
|
||||
--add gun def to the registered table
|
||||
Guns4d.gun.registered[def.name] = def
|
||||
|
||||
--register the visual entity
|
||||
minetest.register_entity(def.name.."_visual", {
|
||||
initial_properties = {
|
||||
|
@ -1,5 +1,6 @@
|
||||
Instantiatable_class = {
|
||||
instance = false
|
||||
instance = false,
|
||||
__no_copy = true
|
||||
}
|
||||
--not that construction change is NOT called for inheriting an object.
|
||||
function Instantiatable_class:inherit(def)
|
||||
@ -7,6 +8,7 @@ function Instantiatable_class:inherit(def)
|
||||
--if not def then def = {} else def = table.shallow_copy(def) end
|
||||
def.parent_class = self
|
||||
def.instance = false
|
||||
def.__no_copy = true
|
||||
def._construct_low = def.construct
|
||||
--this effectively creates a construction chain by overwriting .construct
|
||||
function def.construct(parameters)
|
||||
@ -18,7 +20,6 @@ function Instantiatable_class:inherit(def)
|
||||
self.construct(parameters)
|
||||
end
|
||||
end
|
||||
--print("CONSTRUCTED")
|
||||
def.construct(def)
|
||||
--iterate through table properties
|
||||
setmetatable(def, {__index = self})
|
||||
@ -28,6 +29,7 @@ function Instantiatable_class:new(def)
|
||||
--if not def then def = {} else def = table.shallow_copy(def) end
|
||||
def.base_class = self
|
||||
def.instance = true
|
||||
def.__no_copy = true
|
||||
function def:inherit(def)
|
||||
assert(false, "cannot inherit instantiated object")
|
||||
end
|
||||
|
@ -46,8 +46,11 @@ function player_handler:update(dt)
|
||||
self.model_handler = model_handler.get_handler(self:get_properties().mesh):new({player=self.player})
|
||||
----control handler----
|
||||
self.control_handler = Guns4d.control_handler:new({player=player, controls=self.gun.properties.controls})
|
||||
--reinitialize some handler data and set set_hud_flags
|
||||
|
||||
--this needs to be stored for when the gun is unset!
|
||||
self.horizontal_offset = self.gun.properties.ads.horizontal_offset
|
||||
|
||||
--set_hud_flags
|
||||
player:hud_set_flags({wielditem = false, crosshair = false})
|
||||
|
||||
--for the gun's scopes to work properly we need predictable offsets.
|
||||
@ -84,11 +87,11 @@ function player_handler:update(dt)
|
||||
--eye offsets and ads_location
|
||||
if self.control_bools.ads and (self.ads_location<1) then
|
||||
--if aiming, then increase ADS location
|
||||
self.ads_location = math.clamp(self.ads_location + (dt/self.gun.properties.aim_time), 0, 1)
|
||||
self.ads_location = math.clamp(self.ads_location + (dt/self.gun.properties.ads.aim_time), 0, 1)
|
||||
elseif (not self.control_bools.ads) and self.ads_location>0 then
|
||||
local divisor = .2
|
||||
if self.gun then
|
||||
divisor = self.gun.properties.aim_time/self.gun.consts.AIM_OUT_AIM_IN_SPEED_RATIO
|
||||
divisor = self.gun.properties.ads.aim_time/self.gun.consts.AIM_OUT_AIM_IN_SPEED_RATIO
|
||||
end
|
||||
self.ads_location = math.clamp(self.ads_location - (dt/divisor), 0, 1)
|
||||
end
|
||||
|
@ -40,14 +40,14 @@ function player_model:update()
|
||||
|
||||
local first, second = player:get_eye_offset()
|
||||
local eye_pos = vector.new(0, handler:get_properties().eye_height*10, 0)+first
|
||||
if handler.control_bools.ads then
|
||||
eye_pos.x = handler.horizontal_offset*10
|
||||
end
|
||||
player:set_bone_position("guns3d_hipfire_bone", self.offsets.arm.rltv_right, vector.new(-(pitch*gun.consts.HIP_PLAYER_GUN_ROT_RATIO), 180-player_axial_offset.y, 0))
|
||||
player:set_bone_position("guns3d_reticle_bone", eye_pos, vector.new(combined.x, 180-combined.y, 0))
|
||||
player:set_bone_position("guns3d_head", self.offsets.head, {x=pitch,z=0,y=0})
|
||||
|
||||
local dir = gun:get_player_axial_dir()
|
||||
dir = vector.normalize(dir)
|
||||
local rot = vector.dir_to_rotation(dir)*180/math.pi
|
||||
minetest.chat_send_all(dump(rot))
|
||||
local rot = vector.dir_to_rotation(gun.paxial_dir)*180/math.pi
|
||||
player:set_bone_position("guns3d_aiming_bone", eye_pos, {x=rot.x,y=-rot.y+180,z=0})
|
||||
end
|
||||
function player_model:prepare_deletion()
|
||||
|
@ -2,17 +2,18 @@ Sprite_scope = Instantiatable_class:inherit({
|
||||
images = {
|
||||
fore = {
|
||||
texture = "blank.png",
|
||||
scale = {x=11,y=11},
|
||||
scale = {x=13,y=13},
|
||||
movement_multiplier = 1,
|
||||
},
|
||||
back = {
|
||||
texture = "blank.png",
|
||||
scale = {x=10,y=10},
|
||||
movement_multiplier = -1,
|
||||
opacity_delay = 2,
|
||||
},
|
||||
reticle = {
|
||||
texture = "gun_mrkr.png",
|
||||
scale = {x=1,y=1},
|
||||
scale = {x=.5,y=.5},
|
||||
movement_multiplier = 1,
|
||||
misalignment_opacity_threshold_angle = 3,
|
||||
misalignment_opacity_maximum_angle = 8,
|
||||
@ -30,12 +31,6 @@ Sprite_scope = Instantiatable_class:inherit({
|
||||
if def.images then
|
||||
def.images = table.fill(new_images, def.images)
|
||||
end
|
||||
def.elements.reticle = def.player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x=.5,y=.5},
|
||||
scale = def.images.reticle.scale,
|
||||
text = "blank.png",
|
||||
}
|
||||
def.elements.fore = def.player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x=.5,y=.5},
|
||||
@ -48,6 +43,12 @@ Sprite_scope = Instantiatable_class:inherit({
|
||||
scale = def.images.back.scale,
|
||||
text = "blank.png",
|
||||
}
|
||||
def.elements.reticle = def.player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x=.5,y=.5},
|
||||
scale = def.images.reticle.scale,
|
||||
text = "blank.png",
|
||||
}
|
||||
end
|
||||
end
|
||||
})
|
||||
@ -60,12 +61,15 @@ function Sprite_scope:update()
|
||||
if handler.ads_location ~= 1 then
|
||||
dir = dir + (self.gun.properties.ads.offset+vector.new(self.gun.properties.ads.horizontal_offset,0,0))*0
|
||||
end
|
||||
local v = Point_to_hud(dir, 80, ratio)
|
||||
self.player:hud_change(self.elements.reticle, "position", {x=(v.x*self.images.reticle.movement_multiplier)+.5, y=(v.y*self.images.reticle.movement_multiplier)+.5})
|
||||
self.player:hud_change(self.elements.fore, "position", {x=(v.x*self.images.fore.movement_multiplier)+.5, y=(v.y*self.images.fore.movement_multiplier)+.5})
|
||||
self.player:hud_change(self.elements.back, "position", {x=(v.x*self.images.back.movement_multiplier)+.5, y=(v.y*self.images.back.movement_multiplier)+.5})
|
||||
local fov = self.player:get_fov()
|
||||
local v1 = Point_to_hud(dir, fov, ratio)
|
||||
local v2 = Point_to_hud(self.gun.local_paxial_dir, fov, ratio)
|
||||
self.player:hud_change(self.elements.fore, "position", {x=(v1.x*self.images.fore.movement_multiplier)+.5, y=(v1.y*self.images.fore.movement_multiplier)+.5})
|
||||
self.player:hud_change(self.elements.back, "position", {x=(v2.x*self.images.back.movement_multiplier)+.5, y=(v2.y*self.images.back.movement_multiplier)+.5})
|
||||
self.player:hud_change(self.elements.reticle, "position", {x=(v1.x*self.images.reticle.movement_multiplier)+.5, y=(v1.y*self.images.reticle.movement_multiplier)+.5})
|
||||
--update textures
|
||||
end
|
||||
local angle =math.sqrt(self.gun.offsets.total_offset_rotation.gun_axial.x^2+self.gun.offsets.total_offset_rotation.gun_axial.y^2)
|
||||
for i, v in pairs(self.elements) do
|
||||
local def = self.images[i]
|
||||
local tex = def.texture
|
||||
@ -73,12 +77,11 @@ function Sprite_scope:update()
|
||||
--25 possible images, instead of 255.
|
||||
local factor = 1
|
||||
if def.misalignment_opacity_threshold_angle then
|
||||
local angle = math.sqrt(self.gun.offsets.total_offset_rotation.gun_axial.x^2+self.gun.offsets.total_offset_rotation.gun_axial.y^2)
|
||||
if def.misalignment_opacity_threshold_angle < angle then
|
||||
factor = (factor - ((angle-def.misalignment_opacity_threshold_angle)/def.misalignment_opacity_maximum_angle))
|
||||
end
|
||||
end
|
||||
self.player:hud_change(v, "text", tex.."^[opacity:"..tostring(math.ceil((25.5*handler.ads_location*factor))*10))
|
||||
self.player:hud_change(v, "text", tex.."^[opacity:"..tostring(math.ceil((25.5*handler.ads_location))*10))
|
||||
end
|
||||
end
|
||||
function Sprite_scope:prepare_deletion()
|
||||
|
176
default_controls.lua
Normal file
176
default_controls.lua
Normal file
@ -0,0 +1,176 @@
|
||||
Guns4d.default_controls = {
|
||||
controls = {}
|
||||
}
|
||||
Guns4d.default_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
|
||||
}
|
||||
Guns4d.default_controls.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
|
||||
}
|
||||
Guns4d.default_controls.reload = {
|
||||
conditions = {"zoom"},
|
||||
loop = false,
|
||||
timer = 0, --1 so we have a call to initialize the timer.
|
||||
func = function(active, interrupted, data, busy_list, handler)
|
||||
local gun = handler.gun
|
||||
local ammo_handler = gun.ammo_handler
|
||||
local props = gun.properties
|
||||
if active then
|
||||
if not data.state then
|
||||
data.state = 0
|
||||
end
|
||||
local this_state = props.reload[data.state]
|
||||
local next_state_index = data.state
|
||||
|
||||
if next_state_index == 0 then
|
||||
|
||||
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" then
|
||||
local pause = false
|
||||
local next = props.reload[next_state_index+1]
|
||||
if (next.type=="load_fractional" or next.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
|
||||
next_state_index = next_state_index +1
|
||||
|
||||
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]
|
||||
|
||||
--check that the next states are actually valid, if not, skip them
|
||||
local valid_state = false
|
||||
while not valid_state do
|
||||
local state_changed = false
|
||||
next_state = props.reload[next_state_index]
|
||||
if next_state then
|
||||
local state_changed = false
|
||||
|
||||
if next_state.type == "unload" then
|
||||
|
||||
if props.ammo.magazine_only and (ammo_handler.ammo.loaded_mag == "empty") then
|
||||
state_changed = true
|
||||
end
|
||||
|
||||
elseif next_state.type == "unload_fractional" then
|
||||
|
||||
if not ammo_handler.ammo.total_bullets > 0 then
|
||||
state_changed = true
|
||||
end
|
||||
|
||||
elseif next_state.type == "load" then
|
||||
if props.ammo.magazine_only then
|
||||
if not ammo_handler:can_load_magazine() then
|
||||
state_changed = true
|
||||
end
|
||||
else
|
||||
|
||||
if not ammo_handler:can_load_flat() then
|
||||
state_changed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if not state_changed then
|
||||
valid_state=true
|
||||
else
|
||||
next_state_index = next_state_index + 1
|
||||
next_state = props.reload[next_state_index]
|
||||
end
|
||||
else
|
||||
data.state = 0
|
||||
data.timer = 0.5
|
||||
data.held = true
|
||||
return
|
||||
end
|
||||
end
|
||||
--check if we're at cycle end
|
||||
if next_state == nil then
|
||||
data.state = 0
|
||||
data.timer = 0
|
||||
data.held = true
|
||||
return
|
||||
else
|
||||
data.state = next_state_index
|
||||
data.timer = next_state.time
|
||||
data.held = false
|
||||
local anim = next_state.anim
|
||||
if type(next_state.anim) == "string" then
|
||||
anim = props.animations[next_state.anim]
|
||||
end
|
||||
gun:set_animation(anim, next_state.time)
|
||||
end
|
||||
elseif interrupted then
|
||||
local this_state = props.reload[data.state]
|
||||
if this_state and (this_state.type == "unload") and (this_state.interupt == "to_ground") then
|
||||
--true indicates to_ground (meaning they will be removed)
|
||||
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
|
||||
gun:clear_animation()
|
||||
data.state = 0
|
||||
end
|
||||
end
|
||||
}
|
||||
Guns4d.default_controls.on_use = function(itemstack, handler, pointed_thing)
|
||||
handler.gun:attempt_fire()
|
||||
handler.control_handler.busy_list.on_use = true
|
||||
end
|
@ -12,12 +12,9 @@
|
||||
|
||||
|
||||
----------------------------------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 derived class to handle ammunition, can be changed for modularity- not reccomended, it should provide everything you need.
|
||||
ammo_handler = Ammo_handler
|
||||
|
||||
--a table containing info about the magazines this gun may use.
|
||||
@ -29,7 +26,7 @@
|
||||
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.
|
||||
--note that if the magazine contains unaccepted bullets (properties.ammo.accepted_bullets), the magazine won't be accepted.
|
||||
accepted_mags = {
|
||||
"modname:itemstring",
|
||||
"modname:itemstring"
|
||||
@ -37,7 +34,7 @@
|
||||
}
|
||||
|
||||
--list of bullets this gun accepts. These must be registered via Guns4d.ammo.register_bullet()
|
||||
accepted_bullets = {
|
||||
ammo.accepted_bullets = {
|
||||
"modname:itemstring",
|
||||
"modname:itemstring"
|
||||
}
|
||||
|
@ -24,20 +24,24 @@ gun features
|
||||
fix shooting through walls be pressing against them
|
||||
correct player look-down
|
||||
gun leaning
|
||||
cap aimed look angles <80 - >-80 (gun bugs out)
|
||||
add hip and aim bone offsets (this is for guns or modifiers that add stuff like "laser sights" or hud that simulates holographic sights, could be cool for futuristic type shooters)
|
||||
movement rotation
|
||||
sprite scopes: fix player aim delay being present with sprite scopes
|
||||
bullets
|
||||
bullet class system
|
||||
add blunt force properties
|
||||
on_hitnode function callback
|
||||
|
||||
compatibility
|
||||
add consts for player inv use in Ammo_handler
|
||||
make monoid for player properties, player look offsets (especially needed for vehicle mods)
|
||||
|
||||
optimization
|
||||
!!hardcore optimization of get_dir() type functions required- super inefficient.
|
||||
hardcore optimization of get_dir() type functions required- super inefficient.
|
||||
optimization of sprite scopes needed, buffering for relative dirs needed for when inactive. Potentially remove the global table for them (if possible.)
|
||||
|
||||
auxillary (beta+/never)
|
||||
player hitboxes
|
||||
|
||||
server to client lag prediction
|
||||
possible user CSM for lag prediction
|
||||
bullet drop (maybe bullet wind?)
|
||||
@ -45,4 +49,6 @@ auxillary (beta+/never)
|
||||
inverse kinematics
|
||||
stamina
|
||||
a better system for reloading magazine (something like a inventory that fractionally reloads magazines so it takes time.)
|
||||
create a non-player gun API
|
||||
create a non-player gun API
|
||||
bullets, mags: add meta data support? (probably not)
|
||||
jamming
|
80
gun_api.lua
80
gun_api.lua
@ -1,80 +0,0 @@
|
||||
local Vec = vector
|
||||
local default_def = {
|
||||
--name = <string>
|
||||
--itemstring = <string>
|
||||
--textures = {<textures>}
|
||||
--mesh = <meshname> (media)
|
||||
hip = {
|
||||
offset = Vec.new(0,0,.2),
|
||||
},
|
||||
ads = {
|
||||
offset = Vec.new(0,0,.1),
|
||||
horizontal_offset = .1,
|
||||
},
|
||||
recoil = {
|
||||
velocity_correction_factor = {
|
||||
gun_axial = 2,
|
||||
player_axial = 2,
|
||||
},
|
||||
target_correction_factor = { --angular correction rate per second: time_since_fire*target_correction_factor
|
||||
gun_axial = 30,
|
||||
player_axial = 1,
|
||||
},
|
||||
target_correction_max_rate = { --the cap for time_since_fire*target_correction_factor
|
||||
gun_axial = 100,
|
||||
player_axial = 6,
|
||||
},
|
||||
angular_velocity_max = {
|
||||
gun_axial = 0,
|
||||
player_axial = 0,
|
||||
},
|
||||
angular_velocity = {
|
||||
gun_axial = {x=.1, y=.1},
|
||||
player_axial = {x=.1, y=.1},
|
||||
},
|
||||
},
|
||||
firerateRPM = 600,
|
||||
consts = {
|
||||
HIP_PLAYER_GUN_ROT_RATIO = .6
|
||||
},
|
||||
aim_time = .5
|
||||
}
|
||||
local valid_ctrls = {
|
||||
up=true,
|
||||
down=true,
|
||||
left=true,
|
||||
right=true,
|
||||
jump=true,
|
||||
aux1=true,
|
||||
sneak=true,
|
||||
dig=true,
|
||||
place=true,
|
||||
LMB=true,
|
||||
RMB=true,
|
||||
zoom=true,
|
||||
}
|
||||
function Guns4d.register_gun_default(def)
|
||||
assert(def, "no definition table provided")
|
||||
assert(def.name, "no name provided when registering gun")
|
||||
assert(def.itemstring, "no itemstring provided when registering gun")
|
||||
local new_def = {}
|
||||
new_def.consts = def.consts
|
||||
new_def.name = def.name; def.name = nil
|
||||
new_def.itemstring = def.itemstring; def.itemstring = nil
|
||||
new_def.properties = table.fill(default_def, def)
|
||||
--validate controls
|
||||
if new_def.properties.controls then
|
||||
for i, control in pairs(new_def.properties.controls) 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
|
||||
if not valid_ctrls[condition] then
|
||||
assert(false, "invalid key: '"..condition.."'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--gun is registered within this function
|
||||
Guns4d.gun:inherit(new_def)
|
||||
end
|
2
init.lua
2
init.lua
@ -5,7 +5,7 @@ Guns4d = {
|
||||
local path = minetest.get_modpath("guns4d")
|
||||
dofile(path.."/misc_helpers.lua")
|
||||
dofile(path.."/visual_effects.lua")
|
||||
dofile(path.."/gun_api.lua")
|
||||
dofile(path.."/default_controls.lua")
|
||||
dofile(path.."/block_values.lua")
|
||||
dofile(path.."/register_ammo.lua")
|
||||
path = path .. "/classes"
|
||||
|
@ -152,7 +152,7 @@ function table.tostring(tbl, shallow, tables, depth)
|
||||
end
|
||||
|
||||
|
||||
--replace elements in tbl with elements in replacement, but preserve the rest
|
||||
--replace fields (and fill sub-tables) in `tbl` with elements in `replacement`. Recursively iterates all sub-tables. use property __overfill=true for subtables that don't want to be overfilled.
|
||||
function table.fill(tbl, replacement, preserve_reference, indexed_tables)
|
||||
if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing
|
||||
local new_table = tbl
|
||||
@ -161,13 +161,23 @@ function table.fill(tbl, replacement, preserve_reference, indexed_tables)
|
||||
end
|
||||
for i, v in pairs(replacement) do
|
||||
if new_table[i] then
|
||||
if type(v) == "table" and type(replacement[i]) == "table" then
|
||||
if not indexed_tables[v] then
|
||||
indexed_tables[v] = true
|
||||
new_table[i] = table.fill(tbl[i], replacement[i], false, indexed_tables)
|
||||
local replacement_type = type(v)
|
||||
if replacement_type == "table" then
|
||||
if type(new_table[i]) == "table" then
|
||||
if not indexed_tables[v] then
|
||||
if not new_table[i].__overfill then
|
||||
indexed_tables[v] = true
|
||||
new_table[i] = table.fill(tbl[i], replacement[i], false, indexed_tables)
|
||||
else --if overfill is present, we don't want to preserve the old table.
|
||||
new_table[i] = table.deep_copy(replacement[i])
|
||||
end
|
||||
end
|
||||
elseif not replacement[i].__no_copy then
|
||||
new_table[i] = table.deep_copy(replacement[i])
|
||||
else
|
||||
new_table[i] = replacement[i]
|
||||
end
|
||||
elseif type(replacement[i]) == "table" then
|
||||
new_table[i] = table.deep_copy(replacement[i])
|
||||
new_table[i].__overfill = nil
|
||||
else
|
||||
new_table[i] = replacement[i]
|
||||
end
|
||||
|
@ -21,6 +21,8 @@ Guns4d.ammo = {
|
||||
}
|
||||
}
|
||||
local max_wear = 65535
|
||||
function Guns4d.ammo.on_hit_player(bullet, force_mmRHA)
|
||||
end
|
||||
function Guns4d.ammo.register_bullet(def)
|
||||
assert(def.itemstring, "no itemstring")
|
||||
assert(minetest.registered_items[def.itemstring], "no item '"..def.itemstring.."' found. Must be a registered item (check dependencies?)")
|
||||
@ -35,6 +37,7 @@ function Guns4d.ammo.initialize_mag_data(itemstack, meta)
|
||||
return itemstack
|
||||
end
|
||||
function Guns4d.ammo.update_mag(def, itemstack, meta)
|
||||
def = def or Guns4d.ammo.registered_magazines[itemstack:get_name()]
|
||||
meta = meta or itemstack:get_meta()
|
||||
local bullets = minetest.deserialize(meta:get_string("guns4d_loaded_bullets"))
|
||||
local count = 0
|
||||
@ -43,7 +46,8 @@ function Guns4d.ammo.update_mag(def, itemstack, meta)
|
||||
current_bullet = i
|
||||
count = count + v
|
||||
end
|
||||
itemstack:set_wear((max_wear-(max_wear*count/def.capacity))+1)
|
||||
local new_wear = max_wear-(max_wear*count/def.capacity)
|
||||
itemstack:set_wear(math.clamp(new_wear, 1, max_wear-1))
|
||||
meta:set_int("guns4d_total_bullets", count)
|
||||
meta:set_string("guns4d_next_bullet", current_bullet)
|
||||
return itemstack
|
||||
@ -88,7 +92,6 @@ function Guns4d.ammo.register_magazine(def)
|
||||
Guns4d.ammo.initialize_mag_data(v)
|
||||
end
|
||||
end
|
||||
print(num_mags)
|
||||
if num_mags > 0 then
|
||||
if itemstack:get_name()=="" then
|
||||
for i, v in pairs(craft_inv:get_list("craft")) do
|
||||
@ -100,7 +103,6 @@ function Guns4d.ammo.register_magazine(def)
|
||||
return
|
||||
end
|
||||
if (name~="") and (not (name == def.itemstring)) and (not def.accepted_bullets_set[name]) then
|
||||
print("name:", dump(def.accepted_bullets_set))
|
||||
return
|
||||
end
|
||||
end
|
||||
@ -124,7 +126,6 @@ function Guns4d.ammo.register_magazine(def)
|
||||
end
|
||||
if not def.accepted_bullets_set[name] then
|
||||
if (name ~= "") and (name~=def.itemstring) then
|
||||
print("return", "'"..name.."'")
|
||||
return
|
||||
end
|
||||
end
|
||||
@ -161,7 +162,6 @@ function Guns4d.ammo.register_magazine(def)
|
||||
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
|
||||
end)
|
||||
|
BIN
textures/scope.png
Normal file
BIN
textures/scope.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -6,8 +6,8 @@ function Guns4d.muzzle_flash(self)
|
||||
end
|
||||
local dir, offset_pos = self.dir, self:get_pos(self.properties.flash_offset)
|
||||
offset_pos=offset_pos+self.player:get_pos()
|
||||
local min = vector.rotate(vector.new(-2, -2, -.3), vector.dir_to_rotation(dir))
|
||||
local max = vector.rotate(vector.new(2, 2, .3), vector.dir_to_rotation(dir))
|
||||
local min = vector.rotate(vector.new(-1, -1, -.15), {x=0,y=self.offsets.player_rotation.y,z=0})
|
||||
local max = vector.rotate(vector.new(1, 1, .15), {x=0,y=self.offsets.player_rotation.y,z=0})
|
||||
minetest.add_particlespawner({
|
||||
exptime = .18,
|
||||
time = .1,
|
||||
@ -16,7 +16,7 @@ function Guns4d.muzzle_flash(self)
|
||||
pos = self.properties.flash_offset,
|
||||
radius = .04,
|
||||
glow = 3.5,
|
||||
vel = {min=vector.new(-1, -1, -.15), max=vector.new(1, 1, .15), bias=0},
|
||||
vel = {min=min, max=max, bias=0},
|
||||
texpool = {
|
||||
{ name = "smoke.png", alpha_tween = {.25, 0}, scale = 2, blend = "alpha",
|
||||
animation = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user