signfifficantly changed class & structure system, began work on ammo systems

This commit is contained in:
FatalErr42O 2023-08-09 18:26:50 -07:00
parent 00ea0ff13e
commit a71c84a524
15 changed files with 388 additions and 73 deletions

View File

@ -1,9 +1,10 @@
local Vec = vector
local gun = {
local gun_default = {
--itemstack = Itemstack
--gun_entity = ObjRef
name = "__template__",
registered = {},
property_modifiers = {},
properties = {
hip = {
offset = Vec.new(),
@ -55,12 +56,11 @@ local gun = {
flash_offset = Vec.new(),
aim_time = 1,
firerateRPM = 10000,
controls = {}
controls = {},
accepted_mags = {}
},
offsets = {
pos = Vec.new(),
player_rotation = Vec.new(),
dir = Vec.new(),
--I'll need all three of them, do some precalculation.
total_offset_rotation = {
gun_axial = Vec.new(),
@ -93,9 +93,6 @@ local gun = {
player_axial = Vec.new(),
},
},
particle_spawners = {
--muzzle_smoke
},
--magic number BEGONE
consts = {
HIP_PLAYER_GUN_ROT_RATIO = .75,
@ -106,7 +103,9 @@ local gun = {
HAS_BREATHING = true,
HAS_SWAY = true,
HAS_WAG = true,
INFINITE_AMMO_IN_CREATIVE = true,
},
particle_spawners = {},
walking_tick = 0,
time_since_last_fire = 0,
time_since_creation = 0,
@ -114,7 +113,9 @@ local gun = {
muzzle_flash = Guns4d.muzzle_flash
}
function gun:fire()
function gun_default:spend_round()
end
function gun_default:fire()
assert(self.instance, "attempt to call object method on a class")
if self.rechamber_time <= 0 then
local dir = self.dir
@ -138,7 +139,7 @@ function gun:fire()
end
end
function gun:recoil()
function gun_default:recoil()
assert(self.instance, "attempt to call object method on a class")
for axis, recoil in pairs(self.velocities.recoil) do
for _, i in pairs({"x","y"}) do
@ -148,7 +149,7 @@ function gun:recoil()
self.time_since_last_fire = 0
end
function gun:get_dir(rltv)
function gun_default:get_dir(rltv)
assert(self.instance, "attempt to call object method on a class")
local player = self.player
local player_rotation
@ -161,24 +162,11 @@ function gun:get_dir(rltv)
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.gun_axial.x+rotation.player_axial.x+player_rotation.x)*math.pi/180), z=0}))
dir = Vec.rotate(dir, {y=((rotation.gun_axial.y+rotation.player_axial.y+player_rotation.y)*math.pi/180), x=0, z=0})
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})
if not false then
local hud = player:hud_add({
hud_elem_type = "image_waypoint",
text = "muzzle_flash2.png",
world_pos = hud_pos,
scale = {x=10, y=10},
alignment = {x=0,y=0},
offset = {x=0,y=0},
})
minetest.after(0, function(hud)
player:hud_remove(hud)
end, hud)
end
return dir
end
function gun:get_pos(added_pos)
function gun_default:get_pos(added_pos)
assert(self.instance, "attempt to call object method on a class")
added_pos = Vec.new(added_pos)
local player = self.player
@ -217,7 +205,7 @@ function gun:get_pos(added_pos)
return bone_pos+gun_offset+handler:get_pos(), bone_pos, gun_offset
end
function gun:add_entity()
function gun_default:add_entity()
assert(self.instance, "attempt to call object method on a class")
self.entity = minetest.add_entity(self.player:get_pos(), self.name.."_visual")
local obj = self.entity:get_luaentity()
@ -225,7 +213,7 @@ function gun:add_entity()
obj:on_step()
end
function gun:has_entity()
function gun_default:has_entity()
assert(self.instance, "attempt to call object method on a class")
if not self.entity then return false end
if not self.entity:get_pos() then return false end
@ -233,7 +221,7 @@ function gun:has_entity()
end
--update the gun, da meat and da potatoes
function gun:update(dt)
function gun_default:update(dt)
assert(self.instance, "attempt to call object method on a class")
if not self:has_entity() then self:add_entity() end
self.pos = self:get_pos()
@ -269,11 +257,6 @@ function gun:update(dt)
--sprite scope
if self.properties.sprite_scope then
if not self.sprite_scope then
self.sprite_scope = self.properties.sprite_scope:new({
gun = self
})
end
self.sprite_scope:update()
end
@ -282,7 +265,8 @@ function gun:update(dt)
total_rot.player_axial = offsets.recoil.player_axial + offsets.walking.player_axial + offsets.sway.player_axial + {x=offsets.breathing.player_axial,y=0,z=0} + {x=0,y=0,z=0}
total_rot.gun_axial = offsets.recoil.gun_axial + offsets.walking.gun_axial + offsets.sway.gun_axial
end
function gun:update_wag(dt)
function gun_default:update_wag(dt)
local handler = self.handler
if handler.walking then
self.walking_tick = self.walking_tick + (dt*Vec.length(self.player:get_velocity()))
@ -314,7 +298,8 @@ function gun:update_wag(dt)
end
end
end
function gun:update_recoil(dt)
function gun_default:update_recoil(dt)
for axis, _ in pairs(self.offsets.recoil) do
for _, i in pairs({"x","y"}) do
local recoil = self.offsets.recoil[axis][i]
@ -351,7 +336,8 @@ function gun:update_recoil(dt)
end
end
end
function gun:update_breathing(dt)
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-
--"length" (aka rate-pause), thus it will pi/length or pi/(rate-pause) will represent out slope of our control.
@ -364,7 +350,8 @@ function gun:update_breathing(dt)
self.offsets.breathing.player_axial = scale*(math.sin(x))
end
end
function gun:update_sway(dt)
function gun_default:update_sway(dt)
for axis, sway in pairs(self.offsets.sway) do
local sway_vel = self.velocities.sway[axis]
local ran
@ -384,18 +371,25 @@ function gun:update_sway(dt)
self.velocities.sway[axis] = sway_vel
end
end
function gun:prepare_deletion()
function gun_default:prepare_deletion()
assert(self.instance, "attempt to call object method on a class")
if self:has_entity() then self.entity:remove() end
if self.sprite_scope then self.sprite_scope:prepare_deletion() end
end
--construction for the gun class
gun.construct = function(def)
--construction for the base gun class
gun_default.construct = function(def)
if def.instance then
--remember to give gun an id
assert(def.itemstack, "no itemstack provided for initialized object")
assert(def.player, "no player provided")
--make some quick checks.
assert(def.handler, "no player handler object provided")
--initialize some variables
def.player = def.handler.player
local meta = def.itemstack:get_meta()
def.meta = meta
local out = {}
--create ID so we can track switches between weapons
if meta:get_string("guns4d_id") == "" then
local id = tostring(Unique_id.generate())
meta:set_string("guns4d_id", id)
@ -404,30 +398,55 @@ gun.construct = function(def)
else
def.id = meta:get_string("guns4d_id")
end
--make sure there's nothing missing, aka copy over all of the properties.
def.properties = table.fill(gun.properties, def.properties)
--so, we copy the offsets table so we have all of the offsets
--then we create new vectors for gun_axial and player_axial.
def.offsets = table.deep_copy(gun.offsets)
for i, tbl in pairs(def.offsets) do
if tbl.gun_axial and tbl.player_axial and (not i=="breathing") then
tbl.gun_axial = Vec.new(tbl.gun_axial)
tbl.player_axial = Vec.new(tbl.player_axial)
--unavoidable table instancing
def.properties = table.fill(def.base_class.properties, def.properties)
def.particle_spawners = {} --Instantiatable_class only shallow copies. So tables will not change, and thus some need to be initialized.
def.property_modifiers = {}
--initialize all offsets
--def.offsets = table.deep_copy(def.base_class.offsets)
def.offsets = {}
for i, tbl in pairs(def.base_class.offsets) do
if (tbl.gun_axial and tbl.player_axial) then
local ty = type(tbl.gun_axial)
if (ty=="table") and tbl.gun_axial.x and tbl.gun_axial.y and tbl.gun_axial.z then
def.offsets[i] = {}
def.offsets[i].gun_axial = Vec.new()
def.offsets[i].player_axial = Vec.new()
else
def.offsets[i] = {}
def.offsets[i] = table.deep_copy(def.offsets[i])
end
elseif tbl.x and tbl.y and tbl.z then
def.offsets[i] = Vec.new()
end
end
def.velocities = table.deep_copy(gun.velocities)
for i, tbl in pairs(def.velocities) do
--def.velocities = table.deep_copy(def.base_class.velocities)
def.velocities = {}
for i, tbl in pairs(def.base_class.velocities) do
def.velocities[i] = {}
if tbl.gun_axial and tbl.player_axial then
tbl.gun_axial = Vec.new(tbl.gun_axial)
tbl.player_axial = Vec.new(tbl.player_axial)
def.velocities[i].gun_axial = Vec.new()
def.velocities[i].player_axial = Vec.new()
end
end
--properties have been assigned, create necessary objects
if def.properties.sprite_scope then
if not def.sprite_scope then
def.sprite_scope = def.properties.sprite_scope:new({
gun = def
})
end
end
elseif def.name ~= "__template__" 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
@ -445,8 +464,9 @@ gun.construct = function(def)
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 {})
--(this tableref is ephermeral after constructor is called, see instantiatable_class)
Guns4d.gun.registered[def.name] = def
minetest.register_entity(def.name.."_visual", {
initial_properties = {
@ -480,14 +500,14 @@ gun.construct = function(def)
if handler.control_bools.ads then
local normal_pos = (props.ads.offset+Vec.new(props.ads.horizontal_offset,0,0))*10
obj:set_attach(player, gun.consts.AIMING_BONE, normal_pos, -axial_rot, visibility)
obj:set_attach(player, lua_object.consts.AIMING_BONE, normal_pos, -axial_rot, visibility)
else
local normal_pos = Vec.new(props.hip.offset)*10
-- Vec.multiply({x=normal_pos.x, y=normal_pos.z, z=-normal_pos.y}, 10)
obj:set_attach(player, gun.consts.HIPFIRE_BONE, normal_pos, -axial_rot, visibility)
obj:set_attach(player, lua_object.consts.HIPFIRE_BONE, normal_pos, -axial_rot, visibility)
end
end
})
end
end
Guns4d.gun = Instantiatable_class:inherit(gun)
Guns4d.gun = Instantiatable_class:inherit(gun_default)

56
classes/Gun_ammo.lua Normal file
View File

@ -0,0 +1,56 @@
Gun_ammo = Instantiatable_class:inherit({
name = "Gun_ammo_handler",
construct = function(def)
assert(def.gun)
def.itemstack = def.gun.itemstack
def.handler = def.gun.handler
def.inventory = def.handler.inventory
local meta = def.gun.meta
if gun.properties.magazine then
local mag_meta = meta:get_string("guns4d_loaded_mag")
if mag_meta == "" then
meta:set_string("guns4d_loaded_mag", gun.properties.magazine.comes_with or "empty")
meta:set_string("guns4d_loaded_bullets", minetest.serialize({}))
else
def.mag = mag_meta
def.bullets = minetest.deserialize(meta:get_string("guns4d_loaded_bullets"))
end
else
local bullet_meta = meta:get_string("guns4d_loaded_bullets")
if bullet_meta == "" then
meta:set_string("guns4d_loaded_bullets", minetest.serialize({}))
else
def.ammo.bullets = minetest.deserailize(bullet_meta)
end
end
end
})
function Gun_ammo:load_mag()
local inv = self.inventory
for _, ammunition in pairs(self.gun.accepted_mags) do
for i = 1, inv:get_size("main") do
end
end
if magstack then
ammo_table = minetest.deserialize(magstack:get_meta():get_string("ammo"))
inv:set_stack("main", index, "")
state = next_state
state_changed = true
end
end
function Gun_ammo:unload_mag()
end
function Gun_ammo:load_magless()
end
function Gun_ammo:unload_magless()
end
function Gun_ammo:load_fractional()
end
function Gun_ammo:unload_fractional()
end
function Gun_ammo:unload_chamber()
end

View File

@ -4,7 +4,8 @@ Instantiatable_class = {
--not that construction change is NOT called for inheriting an object.
function Instantiatable_class:inherit(def)
--construction chain for inheritance
if not def then def = {} else def = table.shallow_copy(def) end
--if not def then def = {} else def = table.shallow_copy(def) end
def.parent_class = self
def.instance = false
def._construct_low = def.construct
--this effectively creates a construction chain by overwriting .construct
@ -17,13 +18,15 @@ function Instantiatable_class:inherit(def)
self.construct(parameters)
end
end
--print("CONSTRUCTED")
def.construct(def)
--iterate through table properties
setmetatable(def, {__index = self})
return def
end
function Instantiatable_class:new(def)
if not def then def = {} else def = table.shallow_copy(def) end
--if not def then def = {} else def = table.shallow_copy(def) end
def.base_class = self
def.instance = true
function def:inherit(def)
assert(false, "cannot inherit instantiated object")

49
classes/Modifier.lua Normal file
View File

@ -0,0 +1,49 @@
function table.resolve_string(object, address)
local indexes = string.split(address)
local current
for i, v in pairs(indexes) do
current = current[i]
end
return current
end
local function split_into_adresses(object, path, out)
out = out or {}
path = path or ""
for index, val in pairs(object) do
local this_path = path.."."..index
if type(val) == "table" then
end
end
return out
end
Modifier = Instantiatable_class:inherit({
overwrites = {},
construct = function(def)
if def.instance then
assert(type(def.apply)=="function", "no application function found for modifier")
assert(def.name, "name is required for modifiers")
assert(def.properties, "cannot modify a nonexisent properties table")
local old_apply = def.apply
def.is_active = false
def.immutable_props = Proxy_table:get_or_create()
function def.apply(properties)
assert(not def.is_active, "attempt to double apply modifier '"..def.name.."'")
def.is_active = true
local proxy = Proxy_table:get_or_create(properties) --the proxy prevents unintended modification of the original table.
local add_table, override_table = old_apply(proxy)
if add_table then
end
if override_table then
end
end
function def.stop()
end
end
end,
})

View File

@ -20,7 +20,7 @@ function player_handler:update(dt)
assert(self.instance, "attempt to call object method on a class")
local player = self.player
self.wielded_item = self.player:get_wielded_item()
local held_gun = self:is_holding_Gun() --get the gun class that is associated with the held gun
local held_gun = self:is_holding_gun() --get the gun class that is associated with the held gun
if held_gun then
--was there a gun last time? did the wield index change?
local old_index = self.wield_index
@ -28,22 +28,21 @@ function player_handler:update(dt)
--initialize all handlers and objects
if (not self.gun) or (self.gun.id ~= self.wielded_item:get_meta():get_string("guns4d_id")) then
--initialize all handlers
--initialize important player data
self.itemstack = self.wielded_item
self.inventory = player:get_inventory()
----gun (handler w/physical manifestation)----
if self.gun then --delete gun object if present
self.gun:prepare_deletion()
self.gun = nil
end
self.gun = held_gun:new({itemstack=self.wielded_item, player=self.player, handler=self}) --this will set itemstack meta, and create the gun based off of meta and other data.
self.gun = held_gun:new({itemstack=self.wielded_item, handler=self}) --this will set itemstack meta, and create the gun based off of meta and other data.
----model handler----
if self.model_handler then --if model_handler present, then delete
self.model_handler:prepare_deletion()
self.model_handler = nil
end
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
@ -51,7 +50,6 @@ function player_handler:update(dt)
player:hud_set_flags({wielditem = false, crosshair = false})
end
--update some properties.
self.look_rotation.x, self.look_rotation.y = player:get_look_vertical()*180/math.pi, -player:get_look_horizontal()*180/math.pi
if TICK % 10 == 0 then
@ -163,7 +161,7 @@ function player_handler:set_properties(properties)
self.player:set_properties(properties)
self.properties = table.fill(self.properties, properties)
end
function player_handler:is_holding_Gun()
function player_handler:is_holding_gun()
assert(self.instance, "attempt to call object method on a class")
if self.wielded_item then
for name, obj in pairs(Guns4d.gun.registered) do

58
classes/Proxy_table.lua Normal file
View File

@ -0,0 +1,58 @@
Proxy_table = {
registered_proxies = {},
proxy_children = {}
}
--this creates proxy tables in a structure of tables
--this is great if you want to prevent the change of a table
--but still want it to be viewable, such as with constants
function Proxy_table:new(og_table, parent)
local new = {}
self.registered_proxies[og_table] = new
if parent then
self.proxy_children[parent][og_table] = true
else
self.proxy_children[og_table] = {}
parent = og_table
end
--set the proxy's metatable
setmetatable(new, {
__index = function(t, key)
if type(og_table[key]) == "table" then
return Proxy_table:get_or_create(og_table[key], parent)
else
return og_table[key]
end
end,
__newindex = function(table, key)
assert(false, "attempt to edit immutable table, cannot edit a proxy")
end,
})
--[[overwrite og_table meta to destroy the proxy aswell (but I realized it wont be GCed unless it's removed altogether, so this is pointless)
local mtable = getmetatable(og_table)
local old_gc = mtable.__gc
mtable.__gc = function(t)
self.registered_proxies[t] = nil
self.proxy_children[t] = nil
old_gc(t)
end
setmetatable(og_table, mtable)]]
--premake proxy tables
for i, v in pairs(og_table) do
if type(v) == "table" then
Proxy_table:get_or_create(v, parent)
end
end
return new
end
function Proxy_table:get_or_create(og_table, parent)
return self.registered_proxies[og_table] or Proxy_table:new(og_table, parent)
end
function Proxy_table:destroy_proxy(parent)
self.registered_proxies[parent] = nil
if self.proxy_children[parent] then
for i, v in pairs(self.proxy_children[parent]) do
Proxy_table:destroy_proxy(i)
end
end
self.proxy_children[parent] = nil
end

View File

@ -2,7 +2,7 @@ Sprite_scope = Instantiatable_class:inherit({
images = {
fore = {
texture = "scope_fore.png",
scale = {x=10,y=10},
scale = {x=11,y=11},
movement_multiplier = 1,
},
back = {

View File

@ -0,0 +1,18 @@
z = reload
tap shift+z = switch fire mode
while aiming
q-e = aux switch leaning side/aiming side (default is always right)
while hip
e = aux
hold shift+z = open gun menu
q = drop the weapon
gun menu
turn safety on (can be turned off with shift+z)
unload weapon
set preferred ammo type(s)
set preferred ammo type weighting(?) (allow for control over when there's not enough ammo in a mag to prefer it)
add modifications

14
docs/modifier class.txt Normal file
View File

@ -0,0 +1,14 @@
The modifier class is a class that's used to specify
specific changes to a gun's properties.
to make a modifier, do something like this
Modifier:new({
apply = function(properties)
--absolutely DO NOT EVER change properties here
--this function is only so you can calculate changes
--based on the properties of the gun.
return {
}
end
})

View File

@ -1,4 +1,6 @@
VFX, SFX:
Bullet hit node FX (steal mostly from 3dguns)
Bullet fly-by SFX
@ -20,6 +22,8 @@ gun features
"3d" optics
attachments (last before beta)
fix shooting through walls be pressing against them
correct player look-down
gun leaning
bullets
bullet class system

View File

@ -53,11 +53,9 @@ local default_def = {
if not handler.control_handler.busy_list.on_use then
handler.gun:fire()
end
print(handler.control_handler.busy_list.on_use)
end
},
on_use = function(itemstack, handler, pointed_thing)
print("use")
handler.gun:fire()
handler.control_handler.busy_list.on_use = true
end

View File

@ -15,6 +15,7 @@ dofile(path.."/Sprite_scope.lua")
dofile(path.."/Gun.lua")
dofile(path.."/Player_model_handler.lua")
dofile(path.."/Player_handler.lua")
dofile(path.."/Proxy_table.lua")
--load after
path = minetest.get_modpath("guns4d")

View File

@ -58,6 +58,56 @@ function table.deep_copy(tbl, copy_metatable, indexed_tables)
end
return new_table
end
function table.contains(tbl, value)
for i, v in pairs(tbl) do
if v == value then
return i
end
end
return false
end
local function parse_index(i)
if type(i) == "string" then
return "[\""..i.."\"]"
else
return "["..tostring(i).."]"
end
end
--dump() sucks.
function table.tostring(tbl, shallow, tables, depth)
--create a list of tables that have been tostringed in this chain
if not table then return "nil" end
if not tables then tables = {this_table = tbl} end
if not depth then depth = 0 end
depth = depth + 1
local str = "{"
local initial_string = "\n"
for i = 1, depth do
initial_string = initial_string .. " "
end
for i, v in pairs(tbl) do
local val_type = type(v)
if val_type == "string" then
str = str..initial_string..parse_index(i).." = \""..v.."\","
elseif val_type == "table" and (not shallow) then
local contains = table.contains(tables, v)
--to avoid infinite loops, make sure that the table has not been tostringed yet
if not contains then
tables[i] = v
str = str..initial_string..parse_index(i).." = "..table.tostring(v, shallow, tables, depth)..","
else
str = str..initial_string..parse_index(i).." = "..tostring(v).." ("..contains.."),"
end
else
str = str..initial_string..parse_index(i).." = "..tostring(v)..","
end
end
return str..string.sub(initial_string, 1, -5).."}"
end
--replace elements in tbl with elements in replacement, but preserve the rest
function table.fill(tbl, replacement, preserve_reference, indexed_tables)
if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing

46
register_ammo.lua Normal file
View File

@ -0,0 +1,46 @@
Default_bullet = {
registered = {},
range = 100,
force_mmRHA = 1,
dropoff_mmRHA = 0,
damage = 0,
itemstring = "",
construct = function(def)
assert(not def.instance, "attempt to create instance of a template")
assert(rawget(def, "itemstring"), "no string provided to new bullet template")
assert(minetest.registered_items[def.itemstring], "bullet item is not registered. Check dependencies?")
end
}
Guns4d.ammo = {
registered_bullets = {
},
registered_magazines = {
}
}
function Guns4d.ammo.register_bullet(def)
assert(def.itemstring)
assert(minetest.registered_items[def.itemstring], "no item '"..def.itemstring.."' found. Must be a registered item (check dependencies?)")
Guns4d.ammo.registered_bullets[def.itemstring] = table.fill(Default_bullet, def)
end
function Guns4d.ammo.register_magazine(def)
assert(def.accepted_bullets, "missing property def.accepted_bullets. Need specified bullets to allow for loading")
for i, v in pairs(def.accepted_bullets) do
if not Guns4d.ammo.registered_bullets[v] then print("WARNING! bullet "..v.." not registered! is this a mistake?") end
end
--register craft prediction
minetest.register_craft_predict(function(itemstack, player, old_craft_grid, craft_inv)
if craft_inv:contains_item("craft", def.itemstring) and itemstack:get_name()=="" then
--potentially give predicted ammo gauge here
return def.itemstring
end
end)
minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
if craft_inv:contains_item("craft", def.itemstring) and craft_inv:contains_item("craftpreview", def.itemstring) then
end
end)
--register the actual recipe to add ammo to a mag
end