added blunt, sharp penetration, fixed table fill-ins for property subtables, probably other various fixed

This commit is contained in:
FatalErr42O 2023-08-17 12:18:53 -07:00
parent 4230017960
commit 36e2af9f20
19 changed files with 679 additions and 344 deletions

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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 = {

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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
View 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

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

BIN
textures/scope.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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 = {