account for position in animation, player model support for reflector sights, reflector sight class, versioning system

This commit is contained in:
FatalErr42O 2025-01-03 15:05:47 -08:00
parent 459683814e
commit 4cc3c030a5
24 changed files with 841 additions and 560 deletions

View File

@ -5,7 +5,8 @@
"dump",
"player_api",
"ItemStack",
"leef"
"leef",
"core"
],
"Lua.diagnostics.disable": [
"undefined-field"

View File

@ -1,94 +0,0 @@
--will have to merge with ammo_handler eventually for coherency.
local attachment_handler = leef.class.new_class:inherit({})
Guns4d.attachment_handler = attachment_handler
function attachment_handler:construct()
assert(self.gun, "no gun object provided")
local meta = self.gun.meta
if self.instance then
self.modifier = {}
self.gun.property_modifiers = self.modifier
self.handler = self.gun.handler
if meta:get_string("guns4d_attachments") == "" then
self.attachments = {}
for i, v in pairs(self.gun.properties.inventory.attachment_slots) do
self.attachments[i] = {}
if type(v.default)=="string" then
self:add_attachment(v.default)
end
end
meta:set_string("guns4d_attachments", minetest.serialize(self.attachments))
else
self.attachments = minetest.deserialize(meta:get_string("guns4d_attachments"))
--self:update_meta()
end
end
end
Guns4d.registered_attachments = {}
function attachment_handler.register_attachment(def)
assert(def.itemstring, "itemstring field required")
--assert(def.modifier)
Guns4d.registered_attachments[def.itemstring] = def
end
function attachment_handler:rebuild_modifiers()
--rebuild the modifier
local new_mods = self.modifier
local index = 1
--replace indices with modifiers
for _, v in pairs(self.attachments) do
for name, _ in pairs(v) do
if Guns4d.registered_attachments[name].modifier then
new_mods[index]=Guns4d.registered_attachments[name].modifier
index = index + 1
end
end
end
--remove any remaining modifiers
if index < #new_mods then
for i=index, #new_mods do
new_mods[i]=nil
end
end
self.gun.property_modifiers["attachment_handler"] = self.modifier
end
--returns bool indicating success.
function attachment_handler:add_attachment(itemstack, slot)
assert(self.instance)
itemstack = ItemStack(itemstack)
local stackname = itemstack:get_name()
if self:can_add(itemstack, slot) then
self.attachments[slot][stackname] = itemstack
self:rebuild_modifiers()
return true
else
return false
end
end
function attachment_handler:can_add(itemstack, slot)
assert(self.instance)
local name = itemstack:get_name()
local props = self.gun.properties
--print(slot, dump(self.attachments))
if Guns4d.registered_attachments[name] and (not self.attachments[slot][name]) and (props.inventory.attachment_slots[slot].allowed) then
--check if it's allowed, group check required
for i, v in pairs(props.inventory.attachment_slots[slot].allowed) do
--print(v, name)
if v==name then
return true
end
end
else
return false
end
end
--returns bool indicating success.
function attachment_handler:remove_attachment(itemstack, slot)
assert(self.instance)
local stackname = itemstack:get_name()
if (self.attachments[slot][stackname]) then
self.attachments[slot][stackname] = nil
self:rebuild_modifiers()
else
return false
end
end

View File

@ -8,6 +8,9 @@ local ray = {
--exit_direction = dir,
--range_left = def.bullet.range,
--energy = def.bullet.penetration_RHA
--raw_sharp_damage = 0,
--raw_blunt_damage = 0,
--sharp_penetration = 0,
sharp_to_blunt_conversion_factor = .5, -- 1mmRHA is converted to 1mPA of blunt force
blunt_damage_groups = {}, --minetest.deserialize(Guns4d.config.default_blunt_groups), --these are multiplied by blunt_damage
sharp_damage_groups = {}, --minetest.deserialize(Guns4d.config.default_sharp_groups),

View File

@ -13,7 +13,7 @@ local mat4 = leef.math.mat4
*
]]
local function initialize_data(self)
local function initialize_tracking_meta(self)
--create ID so we can track switches between weapons, also get some other data.
local meta = self.itemstack:get_meta()
self.meta = meta
@ -30,15 +30,18 @@ local function initialize_data(self)
end
end
local function initialize_ammo(self)
self.ammo_handler = self.properties.ammo_handler:new({ --initialize ammo handler from gun and gun metadata.
--initialize the ammo handler
self.ammo_handler = self.properties.subclasses.ammo_handler:new({ --initialize ammo handler from gun and gun metadata.
gun = self
})
local ammo = self.ammo_handler.ammo
self.subclass_instances.ammo_handler = self.ammo_handler
--draw the gun if properties specify it
if self.properties.require_draw_on_swap then
ammo.next_bullet = "empty"
self.ammo_handler.ammo.next_bullet = "empty"
end
minetest.after(0, function() if ammo.total_bullets > 0 then self:draw() end end)
self:update_image_and_text_meta() --has to be called manually in post as ammo_handler would not exist yet.
minetest.after(0, function() if self.ammo_handler.ammo.total_bullets > 0 then self:draw() end end) --call this as soon as the gun is loaded in
--update metadata
self:update_image_and_text_meta()
self.player:set_wielded_item(self.itemstack)
end
@ -71,51 +74,59 @@ local function initialize_physics(self)
end
end
local function initialize_animation(self)
local function initialize_animation_tracking_data(self)
self.animation_data = { --where animations data is stored.
anim_runtime = 0,
runtime = 0,
length = 0,
fps = 0,
frames = {0,0},
frames = {x=0,y=0},
current_frame = 0,
}
self.player_rotation = vector.new(self.properties.initial_vertical_rotation,0,0)
self.animation_rotation = vector.new()
self.animation_translation = vector.new()
end
function gun_default:construct_instance()
assert(self.handler, "no player handler object provided")
--initialize important data.
self.player = self.handler.player
initialize_data(self)
initialize_ammo(self)
--unavoidable table instancing
self.properties = Guns4d.table.fill(self.base_class.properties, self.properties)
--instantiate some tables for runtime data
self.property_modifiers = {}
self.subclass_instances = {}
self.particle_spawners = {}
self.property_modifiers = {}
initialize_animation(self)
initialize_physics(self)
if self.properties.inventory.attachment_slots then
self.attachment_handler = self.properties.attachment_handler:new({
--initialize important stuff
self.player = self.handler.player
self:add_entity()
initialize_tracking_meta(self)
--initialize properties now that any attachments or ammo modifiers have been applied
self._properties_unsafe = Guns4d.table.deep_copy(self.base_class.properties) --we need this copy because proxy tables dont prevent garbage collection
self.properties = leef.class.proxy_table.new(self._properties_unsafe)
--initialize built in subclasses
if self.properties.inventory and self.properties.inventory.part_slots then
self.subclass_instances.part_handler = self.properties.subclasses.part_handler:new({
gun = self
})
end
if self.properties.sprite_scope then
self.sprite_scope = self.properties.sprite_scope:new({
--initialize special subclasses
initialize_ammo(self)
initialize_animation_tracking_data(self)
--initialize any remaining subclasses
for i, class in pairs(self.properties.subclasses) do
if (not self.subclass_instances[i]) and (i~="part_handler") then
self.subclass_instances[i] = class:new({
gun = self
})
end
if self.properties.crosshair then
self.crosshair = self.properties.crosshair:new({
gun = self
})
end
self.part_handler = self.subclass_instances.part_handler
if self.custom_construct then self:custom_construct() end
self:regenerate_properties()
end
--[[
@ -146,7 +157,7 @@ local function validate_controls(props)
--validate controls, done before properties are filled to avoid duplication.
if props.control_actions then
for i, control in pairs(props.control_actions) do
if (i~="on_use") and (i~="on_secondary_use") and (i~="__overfill") then
if (i~="on_use") and (i~="on_secondary_use") and (i~="__replace_old_table") then
assert(control.conditions, "no conditions provided for control")
for _, condition in pairs(control.conditions) do
if not valid_ctrls[condition] then
@ -162,7 +173,8 @@ local function initialize_b3d_animation_data(self, props)
self.b3d_model.global_frames = {
arm_right = {}, --the aim position of the right arm
arm_left = {}, --the aim position of the left arm
rotation = {} --rotation of the gun (this is assumed as gun_axial, but that's probably fucked for holo sight alignments)
root_rotation = {},
root_translation = {}
}
--print(table.tostring(self.b3d_model))
--precalculate keyframe "samples" for intepolation.
@ -186,11 +198,16 @@ local function initialize_b3d_animation_data(self, props)
--we compose it by the inverse because we need to get the global offset in rotation for the animation rotation offset. I really need to comment more often
--print(leef.b3d_nodes.get_node_rotation(nil, main, nil, -1))
local newvec = leef.b3d_nodes.get_node_rotation(nil, main, nil, target_frame)*leef.b3d_nodes.get_node_rotation(nil, main, nil, props.visuals.animations.loaded.x):inverse()
--delta rotation
local this_transform, this_rotation = leef.b3d_nodes.get_node_global_transform(main, target_frame)
local rest_transform, rest_rotation = leef.b3d_nodes.get_node_global_transform(main, props.visuals.animations.loaded.x)
local quat = this_rotation*rest_rotation:inverse()
local vec3 = vector.new(this_transform[13], this_transform[14], this_transform[15])-vector.new(rest_transform[13], rest_transform[14], rest_transform[15]) --extract translation
--used to use euler
table.insert(self.b3d_model.global_frames.rotation, newvec)
table.insert(self.b3d_model.global_frames.root_rotation, quat)
table.insert(self.b3d_model.global_frames.root_translation, vec3)
end
local t, r = leef.b3d_nodes.get_node_global_transform(main, props.visuals.animations.loaded.x,1)
local t, _ = leef.b3d_nodes.get_node_global_transform(main, props.visuals.animations.loaded.x,1)
self.b3d_model.root_orientation_rest = mat4.new(t)
self.b3d_model.root_orientation_rest_inverse = mat4.invert(mat4.new(), t)
@ -277,15 +294,38 @@ local function reregister_item(self, props)
animation = self.properties.visuals.animations.loaded
})
end
--accept a chain of indices where the value from old_index overrides new_index
local function warn_deprecation(gun, field, new_field)
minetest.log("warning", "Guns4d: `"..gun.."` deprecated use of field `"..field.."` use `"..new_field.."` instead.")
end
local function patch_old_gun(self, minor_version)
local props = self.properties
--minor version 2 changes...
if minor_version==2 then
if props.firemode_inventory_overlays then
warn_deprecation(self.name, "firemode_inventory_overlays", "inventory.firemode_inventory_overlays")
for i, _ in pairs(props.firemode_inventory_overlays) do
props.inventory.firemode_inventory_overlays[i] = props.firemode_inventory_overlays[i]
end
end
for _, i in pairs {"ammo_handler", "part_handler", "crosshair", "sprite_scope"} do
if props[i] then
warn_deprecation(self.name, i, "subclasses."..i)
props.subclasses[i] = props[i]
end
end
end
end
--========================== MAIN CLASS CONSTRUCTOR ===============================
function gun_default:construct_base_class()
local props = self.properties
--copy the properties
self.properties = Guns4d.table.fill(self.parent_class.properties, props or {})
self.consts = Guns4d.table.fill(self.parent_class.consts, self.consts or {})
props = self.properties
self._properties_unsafe = Guns4d.table.fill(self.parent_class.properties, self.properties or {})
self.properties = self._properties_unsafe
self._consts_unsafe = Guns4d.table.fill(self.parent_class.consts, self.consts or {})
self.consts = self._consts_unsafe
local props = self.properties
validate_controls(props)
assert((self.properties.recoil.velocity_correction_factor.gun_axial>=1) and (self.properties.recoil.velocity_correction_factor.player_axial>=1), "velocity correction must not be less than one.")
@ -304,7 +344,18 @@ function gun_default:construct_base_class()
for _, v in pairs(self.properties.ammo.accepted_magazines) do
self.accepted_magazines[v] = true
end
self.properties = leef.class.proxy_table:new(self.properties)
--versioning and backwards compatibility stuff
assert(self.consts.VERSION[1]==Guns4d.version[1], "Guns4d gun `"..self.name.." has major version mismatch")
if self.consts.VERSION[1] ~= Guns4d.version[1] then
minetest.log("error", "Guns4d gun `"..self.name.."` minor version mismatch")
end
if self.consts.VERSION[2] < 3 then
minetest.log("error", "Guns4d: `"..self.name.."` had minor version before `1.3.0` indicating that this gun likely has no versioning. Attempting patches for `1.2.0`...")
patch_old_gun(self, 2)
end
self.properties = leef.class.proxy_table.new(self.properties)
self.consts = leef.class.proxy_table.new(self.consts)
Guns4d.gun._registered[self.name] = self --add gun self to the registered table
end

View File

@ -15,9 +15,7 @@ end
-- @tparam float 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(); self:clear_animation() end
local handler = self.handler
if not self:has_entity() then self:add_entity() self:clear_animation() end
--player look rotation. I'm going to keep it real, I don't remember what this math does. Player handler just stores the player's rotation from MT in degrees, which is for some reason inverted
--it's set up like this so that if the gun is fired on auto and the RPM is very fast (faster then globalstep) we know how many rounds to let off.
@ -31,27 +29,102 @@ function gun_default:update(dt)
if self.burst_queue > 0 then self:update_burstfire() end
--update some vectors
self:update_look_offsets(dt)
--I should make this into a list
if self.consts.HAS_SWAY then self:update_sway(dt) end
if self.consts.HAS_RECOIL then self:update_recoil(dt) end
if self.consts.HAS_BREATHING then self:update_breathing(dt) end
if self.consts.HAS_WAG then self:update_wag(dt) end
self:update_animation(dt)
self.dir = self:get_dir()
self.dir = self:get_dir(nil,nil,nil,self.consts.ANIMATIONS_OFFSET_AIM)
self.pos = self:get_pos()+self.handler:get_pos()
--update subclasses
self:update_entity()
if self.properties.sprite_scope then
self.sprite_scope:update()
--this should really be a list of subclasses so its more easily expansible
for i, instance in pairs(self.subclass_instances) do
if instance.update then instance:update(dt) end
if not self.properties.subclasses[i] then
instance:prepare_deletion()
self.subclass_instances[i] = nil
end
if self.properties.crosshair then
self.crosshair:update()
end
--finalize transforms
self:update_transforms()
end
function gun_default:regenerate_properties()
self._properties_unsafe = Guns4d.table.deep_copy(self.base_class.properties)
self.properties = self._properties_unsafe
for i, func in pairs(self.property_modifiers) do
func(self)
end
self.properties = leef.class.proxy_table.new(self.properties)
self:update_visuals()
end
--- not typically called every step, updates the gun object's visuals
function gun_default:update_visuals()
local props = self.properties
self.entity:set_properties({
mesh = props.visuals.mesh,
textures = table.copy(props.visuals.textures),
backface_culling = props.visuals.backface_culling,
visual_size = {x=10*props.visuals.scale,y=10*props.visuals.scale,z=10*props.visuals.scale}
})
for i, ent in pairs(self.attached_objects) do
if not self.properties.visuals.attached_objects[i] then
ent:remove()
end
end
for i, attached in pairs(self.properties.visuals.attached_objects) do
if attached.mesh then
assert(type(attached)=="table", self.name..": `attached.objects` expects a list of tables, incorrect type given.")
local obj
if (not self.attached_objects[i]) or (not self.attached_objects[i]:is_valid()) then
obj = minetest.add_entity(self.handler:get_pos(), "guns4d:gun_entity")
self.attached_objects[i] = obj
else
obj = self.attached_objects[i]
end
obj:set_properties({
mesh = attached.mesh,
textures = table.copy(attached.textures or self.properties.visuals.textures),
backface_culling = attached.backface_culling,
visual_size = {x=attached.scale or 1, y=attached.scale or 1, z=attached.scale or 1}
})
local offset
if attached.offset then
offset = attached.offset
offset = mat4.mul_vec4({}, self.b3d_model.root_orientation_rest_inverse, {offset.x, offset.y, offset.z, 0})
offset = {x=offset[1], y=offset[2], z=offset[3]}
end
local rotation
if attached.rotation then
rotation = attached.rotation
local rotm4 = mat4.set_rot_luanti_entity(mat4.identity(), rotation.x*math.pi/180, rotation.y*math.pi/180, rotation.z*math.pi/180)
rotm4 = self.b3d_model.root_orientation_rest_inverse*rotm4
rotation = {rotm4:get_rot_luanti_entity()}
rotation = {x=rotation[1]*180/math.pi, y=rotation[2]*180/math.pi, z=rotation[3]*180/math.pi}
else
rotation = {(self.b3d_model.root_orientation_rest_inverse):get_rot_luanti_entity()}
rotation = {x=rotation[1]*180/math.pi, y=rotation[2]*180/math.pi, z=rotation[3]*180/math.pi}
end
obj:set_attach(
self.entity,
self.consts.ROOT_BONE,
offset,
rotation
--true
)
else
minetest.log("error", "Guns4d: attached object had no mesh")
end
end
end
--- updates self.total_offsets which stores offsets for bones
function gun_default:update_transforms()
local total_offset = self.total_offsets
@ -76,6 +149,7 @@ end
--- Update and fire the queued weapon burst
function gun_default:update_burstfire()
if self.rechamber_time <= 0 then
local iter = 1
while true do
local success = self:attempt_fire()
if success then
@ -86,6 +160,7 @@ function gun_default:update_burstfire()
end
break
end
iter = iter + 1
end
end
end
@ -132,8 +207,8 @@ function gun_default:update_image_and_text_meta(meta)
for i, v in pairs(self.properties.firemodes) do
firemodes = firemodes+1
end
if firemodes > 1 and self.properties.firemode_inventory_overlays[self.properties.firemodes[self.current_firemode]] then
image = image.."^"..self.properties.firemode_inventory_overlays[self.properties.firemodes[self.current_firemode]]
if firemodes > 1 and self.properties.inventory.firemode_inventory_overlays[self.properties.firemodes[self.current_firemode]] then
image = image.."^"..self.properties.inventory.firemode_inventory_overlays[self.properties.firemodes[self.current_firemode]]
end
if self.handler.infinite_ammo then
image = image.."^"..self.properties.infinite_inventory_overlay
@ -193,6 +268,12 @@ function gun_default:attempt_fire()
self:play_sounds(fire_sound)
self.rechamber_time = self.rechamber_time + (60/props.firerateRPM)
--acount for animation rotation in same update firing
if (self.rechamber_time<(60/props.firerateRPM)) and props.firemodes[self.current_firemode]~="single" then
self.animation_data.runtime = self.animation_data.runtime + (60/props.firerateRPM)
self:update_animation_transforms()
end
return true
end
end
@ -231,6 +312,77 @@ function gun_default:recoil()
end
self.time_since_last_fire = 0
end
function gun_default:open_inventory_menu()
local props = self.properties
local player = self.player
local pname = player:get_player_name()
local inv = minetest.get_inventory({type="player", name=pname})
local window = minetest.get_player_window_information(pname)
local listname = Guns4d.config.inventory_listname
local form_dimensions = {x=20,y=15}
local inv_height=4+((4-1)*.125)
local hotbar_length = player:hud_get_hotbar_itemcount()
local form = "\
formspec_version[7]\
size[".. form_dimensions.x ..",".. form_dimensions.y .."]"
local hotbar_height = math.ceil(hotbar_length/8)
form = form.."\
scroll_container[.25,"..(form_dimensions.y)-inv_height-1.25 ..";10,5;player_inventory;vertical;.05]\
list[current_player;"..listname..";0,0;"..hotbar_length..","..hotbar_height..";]\
list[current_player;"..listname..";0,1.5;8,3;"..hotbar_length.."]\
scroll_container_end[]\
"
if math.ceil(inv:get_size("main")/8) > 4 then
local h = math.ceil(inv:get_size("main")/8)
form=form.."\
scrollbaroptions[max="..h+((h-1)*.125).."]\
scrollbar[10.25,"..(form_dimensions.y)-inv_height-1.25 ..";.5,5;vertical;player_inventory;0]\
"
end
--display gun preview
local len = math.abs(self.model_bounding_box[3]-self.model_bounding_box[6])/props.visuals.scale
local hei = math.abs(self.model_bounding_box[2]-self.model_bounding_box[5])/props.visuals.scale
local offsets = {x=(-self.model_bounding_box[6]/props.visuals.scale)-(len/2), y=(self.model_bounding_box[5]/props.visuals.scale)+(hei/2)}
local meter_scale = 15
local image_scale = meter_scale*(props.inventory.render_size or 1)
local gun_gui_offset = {x=0,y=-2.5}
form = form.."container["..((form_dimensions.x-image_scale)/2)+gun_gui_offset.x.. ","..((form_dimensions.y-image_scale)/2)+gun_gui_offset.y.."]"
if props.inventory.render_image then
form = form.."image["
..(offsets.x*meter_scale) ..","
..(offsets.y*meter_scale) ..";"
..image_scale..","
..image_scale..";"
..props.inventory.render_image.."]"
end
if self.part_handler then
--local attachment_inv = self.part_handler.virtual_inventory
if props.inventory.part_slots and self.part_handler then
for i, attachment in pairs(props.inventory.part_slots) do
form = form.."label["..(image_scale/2)+(attachment.formspec_offset.x or 0)-.75 ..","..(image_scale/2)+(-attachment.formspec_offset.y or 0)-.2 ..";"..(attachment.description or i).."]"
--list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]
local width = attachment.slots or 1
width = width+((width-1)*.125)
form = form.."list[detached:guns4d_attachment_inv_"..pname..";"..i..";"..(image_scale/2)+(attachment.formspec_offset.x or 0)-(width/2)..","..(image_scale/2)+(-attachment.formspec_offset.y or 0)..";3,5;]"
end
end
end
form = form.."container_end[]"
minetest.show_formspec(self.handler.player:get_player_name(), "guns4d:inventory", form)
end
core.register_on_player_receive_fields(function(player, formname, fields)
if formname=="guns4d:inventory" and fields.quit then
local gun = Guns4d.players[player:get_player_name()].gun
gun:regenerate_properties()
end
end)
--- update the offsets of the player's look created by the gun
function gun_default:update_look_offsets(dt)
@ -275,21 +427,21 @@ function gun_default:update_look_offsets(dt)
end
--============================================== positional info =====================================
--all of this dir shit needs to be optimized HARD
function gun_default:get_gun_axial_dir()
--[[function gun_default:get_gun_axial_dir()
assert(self.instance, "attempt to call object method on a class")
local rotation = self.total_offsets
local dir = vector.new(vector.rotate({x=0, y=0, z=1}, {y=0, x=rotation.gun_axial.x*math.pi/180, z=0}))
dir = vector.rotate(dir, {y=rotation.gun_axial.y*math.pi/180, x=0, z=0})
return dir
end
function gun_default:get_player_axial_dir(rltv)
end]]
--[[function gun_default:get_player_axial_dir(rltv)
assert(self.instance, "attempt to call object method on a class")
local handler = self.handler
local rotation = self.total_offsets
local dir = vector.new(vector.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.player_axial.x)*math.pi/180), z=0}))
dir = vector.rotate(dir, {y=((rotation.player_axial.y)*math.pi/180), x=0, z=0})
if not rltv then
if (self.properties.sprite_scope and handler.control_handler.ads) or (self.properties.crosshair and not handler.control_handler.ads) then
if (self.properties.subclasses.sprite_scope and handler.control_handler.ads) or (self.properties.subclasses.crosshair and not handler.control_handler.ads) then
--we need the head rotation in either of these cases, as that's what they're showing.
dir = vector.rotate(dir, {x=handler.look_rotation.x*math.pi/180,y=-handler.look_rotation.y*math.pi/180,z=0})
else
@ -297,44 +449,8 @@ function gun_default:get_player_axial_dir(rltv)
end
end
return dir
end
--This should be replaced with
--[[function gun_default:get_dir(rltv, offset_x, offset_y)
assert(self.instance, "attempt to call object method on a class")
local rotation = self.total_offsets
local handler = self.handler
--rotate x and then y.
--used symbolab.com to precalculate the rotation matrices to save on performance since spread for pellets has to run this.
local p = -(rotation.gun_axial.x+rotation.player_axial.x+(offset_x or 0))*math.pi/180
local y = -(rotation.gun_axial.y+rotation.player_axial.y+(offset_y or 0))*math.pi/180
local Cy = math.cos(y)
local Sy = math.sin(y)
local Cp = math.cos(p)
local Sp = math.sin(p)
local dir = {
x=Sy*Cy,
y=-Sp,
z=Cy*Cp
}
if not rltv then
p = -self.player_rotation.x*math.pi/180
y = -self.player_rotation.y*math.pi/180
Cy = math.cos(y)
Sy = math.sin(y)
Cp = math.cos(p)
Sp = math.sin(p)
dir = vector.new(
(Cy*dir.x)+(Sy*Sp*dir.y)+(Sy*Cp*dir.z),
(dir.y*Cp)-(dir.z*Sp),
(-dir.x*Sy)+(dir.y*Sp*Cy)+(dir.z*Cy*Cp)
)
else
dir = vector.new(dir)
end
return dir
end]]
--probably doesnt get much more optimized then this.
local bone_location = vector.new()
local tmv3_rot = vector.new()
local tmv4_in = {0,0,0,1}
local tmv4_pivot_inv = {0,0,0,0}
@ -346,50 +462,58 @@ local out = vector.new() --reserve the memory, we still want to create new vecto
--gets the gun's position relative to the player. Relative indicates wether it's relative to the player's horizontal look
--offset is relative to the's rotation
--- get the global position of the gun. This is customized to rely on the assumption that there are 3-4 main rotations and 2-3 translations. If the behavior of the bones are changed this method may not work
--- get the global position of the gun. This is customized to rely on the assumption that there are 3-4 main rotations and 2-3 translations. If the behavior of the bones are changed this method may not work.
-- the point of this is to allow the user to find the gun's object origin as well as calculate where a given point should be offset given the parameters.
-- @tparam vec3 offset_pos
-- @tparam bool relative_y wether the y axis is relative to the player's look
-- @tparam bool relative_x wether the x axis is relative to the player's look
-- @tparam bool with_animation wether rotational and translational offsets from the animation are applied
-- @treturn vec3 position of gun (in global or local orientation) relative to the player's position
function gun_default:get_pos(offset, relative_y, relative_x, with_animation)
assert(self.instance, "attempt to call object method on a class")
--local player = self.player
local handler = self.handler
local raw_bone_location = handler.player_model_handler.gun_bone_location
local vs = handler:get_properties().visual_size
bone_location.x, bone_location.y, bone_location.z = raw_bone_location.x, raw_bone_location.y, raw_bone_location.z
local gun_translation = self.gun_translation --needs a refactor
local root_transform = self.b3d_model.root_orientation_rest
offset = offset or empty_vec
--dir needs to be rotated twice seperately to avoid weirdness
local gun_scale = self.properties.visuals.scale
--player look. If its relative on a given axis we eliminate it by setting it to 0.
local px = (relative_x and 0) or nil
local py = (relative_y and 0) or nil
local ax = ((not with_animation) and 0) or nil
local ay = ((not with_animation) and 0) or nil
local az = ((not with_animation) and 0) or nil
--offset is relative to the gun, which is rotated about the origin of the root bone, so we need to make the vector relative to the root's vector, rotate, and then bring it back to global space.
offset = offset or empty_vec
local gun_translation = self.gun_translation --needs a refactor
local root_transform = self.b3d_model.root_orientation_rest
--dir needs to be rotated twice seperately to avoid weirdness
local gun_scale = self.properties.visuals.scale
--generate rotation values based on our output
ttransform=self:get_rotation_transform(ttransform,nil,nil,nil,nil,nil,px,py,ax,ay,az)
--change the pivot of `offset` to the root bone by making our vector relative to it (basically setting it to origin)
tmv4_in[1], tmv4_in[2], tmv4_in[3] = offset.x-root_transform[13]*gun_scale, offset.y-root_transform[14]*gun_scale, offset.z-root_transform[15]*gun_scale
tmv4_offset = ttransform.mul_vec4(tmv4_offset, ttransform, tmv4_in)
tmv4_offset = ttransform.mul_vec4(tmv4_offset, ttransform, tmv4_in) --rotate by our rotation transform
--to bring it back to global space we need to find what we offset it by in `ttransform`'s local space, so we apply the transform to it
tmv4_in[1], tmv4_in[2], tmv4_in[3] = root_transform[13]*gun_scale, root_transform[14]*gun_scale, root_transform[15]*gun_scale
tmv4_pivot_inv = ttransform.mul_vec4(tmv4_pivot_inv, ttransform, tmv4_in) --bring it back to global space by adding what we subtracted earlier (which has now been transformed.)
tmv4_pivot_inv = ttransform.mul_vec4(tmv4_pivot_inv, ttransform, tmv4_in)
--quickly add together tmv4_offset+tmv4_pivot_inv to get the global position of the offset relative to the entity
tmv4_offset[1],tmv4_offset[2],tmv4_offset[3] = tmv4_offset[1]+tmv4_pivot_inv[1], tmv4_offset[2]+tmv4_pivot_inv[2], tmv4_offset[3]+tmv4_pivot_inv[3]
--the translation of the gun is translation of the root bone's coordinate system, so the gun_axial rotation would not affect it, therefore we set local rotations to 0.
--get the position of the gun entity in global space relative to the bone which it is attached to.
ttransform=self:get_rotation_transform(ttransform, 0,0,0,nil,nil,px,py,ax,ay,az)
tmv4_in[1], tmv4_in[2], tmv4_in[3] = gun_translation.x, gun_translation.y, gun_translation.z
tmv4_gun = ttransform.mul_vec4(tmv4_gun, ttransform, tmv4_in)
--the bone location is determined by the rotation of the player alone. This currently only supports player look but will probably support the actual rotation in the future
--get the position of the bone globally
local bone_location = self.handler.player_model_handler.gun_bone_location
if relative_y then
out = vector.new(bone_location)
else
tmv3_rot.y = -handler.look_rotation.y*math.pi/180
tmv3_rot.y = -self.handler.look_rotation.y*math.pi/180
out = vector.rotate(bone_location, tmv3_rot)
end
out.x, out.y, out.z = out.x+tmv4_gun[1]+tmv4_offset[1], out.y+tmv4_gun[2]+tmv4_offset[2], out.z+tmv4_gun[3]+tmv4_offset[3]
--add our global translations together
--bonepos + gunentity + gunoffset + animation offset
local anim = (with_animation and self.animation_translation) or empty_vec
out.x, out.y, out.z = out.x+anim.x+tmv4_gun[1]+tmv4_offset[1], out.y+anim.y+tmv4_gun[2]+tmv4_offset[2], out.z+anim.z+tmv4_gun[3]+tmv4_offset[3]
return out
end
@ -423,12 +547,20 @@ function gun_default:get_rotation_transform(out, lx,ly,lz,gx,gy,px,py,ax,ay,az)
end
local forward = {0,0,1,0}
local tmv4_out = {0,0,0,0}
function gun_default:get_dir(rltv, offx, offy)
-- get the direction for firing
function gun_default:get_dir(rltv, offx, offy, suppress_anim)
local rotations = self.total_offsets
local anim_x = (suppress_anim and 0) or nil
local anim_y = (suppress_anim and 0) or nil
local anim_z = (suppress_anim and 0) or nil
if rltv then
ttransform = self:get_rotation_transform(ttransform, (-rotations.gun_axial.x-(offx or 0) )*trad, (-rotations.gun_axial.y-(offy or 0))*trad, nil, nil, nil, 0, 0, nil,nil,nil)
ttransform = self:get_rotation_transform(ttransform, (-rotations.gun_axial.x-(offx or 0) )*trad, (-rotations.gun_axial.y-(offy or 0))*trad, nil, nil, nil, 0, 0, anim_x,anim_y,anim_z)
else
ttransform = self:get_rotation_transform(ttransform, (-rotations.gun_axial.x-(offx or 0))*trad, (-rotations.gun_axial.y-(offy or 0))*trad, nil, nil, nil, nil, nil, nil,nil,nil)
local player_aim
if (self.properties.subclasses.sprite_scope and self.handler.control_handler.ads) or (self.properties.subclasses.crosshair and not self.handler.control_handler.ads) then
player_aim=self.player:get_look_vertical()
end
ttransform = self:get_rotation_transform(ttransform, (-rotations.gun_axial.x-(offx or 0))*trad, (-rotations.gun_axial.y-(offy or 0))*trad, nil, nil, nil, player_aim, nil, anim_x,anim_y,anim_z)
end
local tmv4 = ttransform.mul_vec4(tmv4_out, ttransform, forward)
local pos = vector.new(tmv4[1], tmv4[2], tmv4[3])
@ -443,15 +575,8 @@ 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(), "guns4d:gun_entity")
local props = self.properties
self.entity:set_properties({
mesh = props.visuals.mesh,
textures = props.visuals.textures,
backface_culling = props.visuals.backface_culling,
visual_size = {x=10*props.visuals.scale,y=10*props.visuals.scale,z=10*props.visuals.scale}
})
Guns4d.gun_by_ObjRef[self.entity] = self
--obj:on_step()
--self:update_entity()
self:update_visuals()
end
local tmp_mat4_rot = mat4.identity()
local ip_time = Guns4d.config.gun_axial_interpolation_time
@ -461,12 +586,11 @@ local ip_time2 = Guns4d.config.translation_interpolation_time
function gun_default:update_entity()
local obj = self.entity
local player = self.player
local axial_rot = self.total_offsets.gun_axial
local handler = self.handler
local props = self.properties
--attach to the correct bone, and rotate
local visibility = true
if self.sprite_scope and self.sprite_scope.hide_gun and (not (self.control_handler.ads_location == 0)) then
if self.subclass_instances.sprite_scope and self.subclass_instances.sprite_scope.hide_gun and (not (self.control_handler.ads_location == 0)) then
visibility = false
end
--Irrlicht uses counterclockwise but we use clockwise.
@ -601,21 +725,19 @@ function gun_default:update_recoil(dt)
self.offsets.recoil[axis][i] = abs*sign
end
end
--print(self.velocities.recoil.player_axial.x, self.velocities.recoil.player_axial.y)
end
--- updates the gun's animation data
-- @tparam dt
function gun_default:update_animation(dt)
local ent = self.entity
--local ent = self.entity
local data = self.animation_data
data.runtime = data.runtime + dt
data.current_frame = Guns4d.math.clamp(data.current_frame+(dt*data.fps), data.frames.x, data.frames.y)
if data.loop and (data.current_frame > data.frames.y) then
data.current_frame = data.frames.x
end
--track rotations and applies to aim.
if self.consts.ANIMATIONS_OFFSET_AIM then self:update_animation_rotation() end
self:update_animation_transforms()
end
--IMPORTANT!!! this does not directly modify the animation_data table anymore, it's all hooked through ObjRef:set_animation() (init.lua) so if animation is set elsewhere it doesnt break.
--this may be deprecated in the future- as it is no longer really needed now that I hook ObjRef functions.
@ -729,40 +851,40 @@ end
--should merge these functions eventually...
function gun_default:update_animation_rotation()
function gun_default:update_animation_transforms()
local current_frame = self.animation_data.current_frame+self.consts.KEYFRAME_SAMPLE_PRECISION
local frame1 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)
local frame2 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)+1
current_frame = current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION
local rotations = self.b3d_model.global_frames.rotation
local out
if rotations then
if rotations[frame1] then
if (not rotations[frame2]) or (current_frame==frame1) then
out = vector.new(rotations[frame1]:get_euler_irrlicht_bone())*180/math.pi
--print("rawsent")
else --to stop nan
local ip_ratio = (current_frame-frame1)/(frame2-frame1)
local rotations = self.b3d_model.global_frames.root_rotation
local positions = self.b3d_model.global_frames.root_translation
local euler_rot
local trans
if not rotations[frame1] then --note that we are inverting the rotations, this is because b3d turns the wrong way or something? It might be an issue with LEEF idk.
euler_rot = vector.new(rotations[1]:get_euler_irrlicht_bone())*-1
else
local ip_ratio = (frame2 and (current_frame-frame1)/(frame2-frame1)) or 1
local vec1 = rotations[frame1]
local vec2 = rotations[frame2]
out = vector.new(vec1:slerp(vec2, ip_ratio):get_euler_irrlicht_bone())*180/math.pi
local vec2 = rotations[frame2] or rotations[frame1]
euler_rot = vector.new(vec1:slerp(vec2, ip_ratio):get_euler_irrlicht_bone())*-180/math.pi
end
if not positions[frame1] then --note that we are inverting the rotations, this is because b3d turns the wrong way or something? It might be an issue with LEEF idk.
trans = positions[1]*-1
else
out = vector.copy(rotations[1])
local ip_ratio = (frame2 and (current_frame-frame1)/(frame2-frame1)) or 1
local vec1 = positions[frame1]
local vec2 = positions[frame2] or positions[frame1]
trans = (vec1*(1-ip_ratio))+(vec2*ip_ratio)
end
--print(frame1, frame2, current_frame, dump(out))
else
out = vector.new()
end
--we use a different rotation system
self.animation_rotation = -out
self.animation_rotation = euler_rot
self.animation_translation = trans
end
--relative to the gun's entity. Returns left, right vectors.
local out = {arm_left=vector.new(), arm_right=vector.new()}
function gun_default:get_arm_aim_pos()
local current_frame = self.animation_data.current_frame
print(current_frame)
local frame1 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)
local frame2 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)+1
current_frame = current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION
@ -776,7 +898,6 @@ function gun_default:get_arm_aim_pos()
local ip_ratio = (current_frame-frame1)/(frame2-frame1)
local vec1 = self.b3d_model.global_frames[i][frame1]
local vec2 = self.b3d_model.global_frames[i][frame2]
--print(current_frame, frame1, frame2, ip_ratio)
out[i] = vec1+((vec1-vec2)*ip_ratio)
end
else
@ -790,10 +911,13 @@ function gun_default:get_arm_aim_pos()
--return vector.copy(self.b3d_model.global_frames.arm_left[1]), vector.copy(self.b3d_model.global_frames.arm_right[1])
end
--- ready the gun to be deleted
function gun_default:prepare_deletion()
self.released = true
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
if self.crosshair then self.crosshair:prepare_deletion() end
for i, instance in pairs(self.subclass_instances) do
if instance.prepare_deletion then instance:prepare_deletion() end
end
end

View File

@ -21,8 +21,10 @@ local Vec = vector
-- where def is the definition of your new gun- or rather the changed parts of it. So to make a new gun you can run Guns4d.gun:inherit()
-- or you can do the same thing with a seperate class of weapons. Set name to "__template" for template classes of guns.
--
-- *Please note:* there are likey undocumented fields that are used in internal functions. If you find one, please make an issue on Github.
-- for properties: for tables where you wish to delete the parent class's fields altogether (since inheritence prevents this) you can set the field "__replace_old_table=true"
-- additionally
--
-- *Please note:* there are likey undocumented fields that are used in internal functions. If you find one, please make an issue on Github.
--
-- @class gun
-- @field properties @{lvl1_fields.properties|properties} which define the vast majority of gun attributes and may change accross instances
@ -66,8 +68,12 @@ local gun_default = {
muzzle_flash = Guns4d.effects.muzzle_flash,
--- `vec3` translation of the gun relative to the "gun" bone or the player axial rotation.
gun_translation = vector.new(),
--- `table` indexed list of modifiers not set by the gun but to be applied to the gun. After changing, gun:update_modifiers() must be called to update it. Also may contain lists of modifiers.
--- `table` indexed list of functions which are called when the gun's properties need to be built. This is used for things like attachments, etc.
property_modifiers = nil,
--- `table` a list of ObjRefs that are attached to the gun as a result of attached_objects
attached_objects = {},
--- `table` list of subclass instances (i.e. Sprite_scope)
subclass_instances = {},
--- properties
--
@ -85,14 +91,6 @@ local gun_default = {
-- @field visuals `table` @{gun.properties.visuals|defines visual attributes of the gun}
-- @compact
properties = {
--- `Ammo_handler` the class (based on) ammo_handler to create an instance of and use. Default is `Guns4d.ammo_handler`
ammo_handler = Guns4d.ammo_handler,
--- `Attachment_handler` attachment_handler class to use. Default is `Guns4d.attachment_handler`
attachment_handler = Guns4d.attachment_handler,
--- `Sprite_scope` sprite scope class to use
sprite_scope = nil,
--- `Dynamic_crosshair` crosshair class to use
crosshair = nil,
--- starting vertical rotation of the gun
initial_vertical_rotation = -60,
--- `float`=.5 max angular deviation (vertical) from breathing
@ -123,7 +121,6 @@ local gun_default = {
burst = 3,
--- `table` containing a list of actions for PC users passed to @{Control_handler}
pc_control_actions = { --used by control_handler
__overfill=true, --this table will not be filled in.
aim = Guns4d.default_controls.aim,
auto = Guns4d.default_controls.auto,
reload = Guns4d.default_controls.reload,
@ -133,7 +130,6 @@ local gun_default = {
},
--- `table` containing a list of actions for touch screen users passed to @{Control_handler}
touch_control_actions = {
__overfill=true,
aim = Guns4d.default_touch_controls.aim,
auto = Guns4d.default_touch_controls.auto,
reload = Guns4d.default_touch_controls.reload,
@ -142,7 +138,7 @@ local gun_default = {
jump_cancel_ads = Guns4d.default_touch_controls.jump_cancel_ads
},
inventory = {
--[[attachment_slots = {
--[[part_slots = {
underbarrel = {
formspec_inventory_location = {x=0, y=1}
slots = 2,
@ -155,7 +151,32 @@ local gun_default = {
},]]
render_size = 2, --length (in meters) which is visible accross the z/forward axis at y/up=0, x=0. For orthographic this will be the scale of the orthographic camera. Default 2
render_image = "m4_ortho.png", --expects an image of the right side of the gun, where the gun is facing the right. Default "m4_ortho.png"
--rendered_from_model = true --if true the rendering is automatically moved to the center of the screen
--- table of firemodes and their overlays in the player's inventory when the gun is on that firemode
firemode_inventory_overlays = { --#4
--singlefire default: "inventory_overlay_single.png"
single = "inventory_overlay_single.png",
--automatic default: "inventory_overlay_auto.png"
auto = "inventory_overlay_auto.png",
--burstfire default: "inventory_overlay_burst.png"
burst = "inventory_overlay_burst.png",
--safe default: "inventory_overlay_safe.png" (unimplemented firemode)
safe = "inventory_overlay_safe.png"
},
},
--- `table` a list of subclasses to create on construct and update. Note special fields `ammo_handler` and `part_handler`.
--
-- @table gun.properties.subclsses
-- @see lvl1_fields.properties|properties
-- @compact
subclasses = {
--- `Ammo_handler` the class (based on) ammo_handler to create an instance of and use. Default is `Guns4d.ammo_handler`
ammo_handler = Guns4d.ammo_handler,
--- `part_handler` Part_handler class to use. Default is `Guns4d.part_handler`
part_handler = Guns4d.part_handler,
--- `Sprite_scope` sprite scope class to use. Nil by default, inherit Sprite_scope for class (**documentation needed, reccomended contact for help if working with it**)
sprite_scope = nil,
--- `Dynamic_crosshair` crosshair class to use. Nil by default, set to `Guns4d.Dynamic_crosshair` for a generic circular expanding reticle.
crosshair = nil,
},
--- properties.ads
--
@ -197,22 +218,6 @@ local gun_default = {
firemodes = { --#3
"single", --not limited to semi-automatic.
},
--- properties.firemode_inventory_overlays
--
-- Defines the overlay on the gun item for each firemode. These are selected automatically by firemode string. Defaults are as follows:
-- @table gun.properties.firemode_inventory_overlays
-- @see lvl1_fields.properties|properties
-- @compact
firemode_inventory_overlays = { --#4
--- singlefire default: "inventory_overlay_single.png"
single = "inventory_overlay_single.png",
--- automatic default: "inventory_overlay_auto.png"
auto = "inventory_overlay_auto.png",
--- burstfire default: "inventory_overlay_burst.png"
burst = "inventory_overlay_burst.png",
--- safe default: "inventory_overlay_safe.png" (unimplemented firemode)
safe = "inventory_overlay_safe.png"
},
--- properties.recoil
--
-- **IMPORTANT**: expects fields to be tables containing a "gun_axial" and "player_axial" field.
@ -357,6 +362,18 @@ local gun_default = {
textures = {},
--- scale multiplier. Default 1
scale = 1,
--- objects that are attached to the gun. This is especially useful for attachments
--
-- @example
-- my_object = {
-- textures = {"blank.png"},
-- visual_size = {x=1,y=1,z=1},
-- offset = {x=0,y=0,z=0},
-- bone = "main",
-- backface_culling = false,
-- glow = 0
-- }
attached_objects = {},
--- toggles backface culling. Default true
backface_culling = true,
--- a table of animations. Indexes define the name of the animation to be refrenced by other functions of the gun.
@ -381,7 +398,7 @@ local gun_default = {
sounds = { --this does not contain reload sound effects.
fire = {
{
__overfill=true,
__replace_old_table=true,
sound = "ar_firing",
max_hear_distance = 40, --far min_hear_distance is also this.
pitch = {
@ -394,7 +411,7 @@ local gun_default = {
}
},
{
__overfill=true,
__replace_old_table=true,
sound = "ar_firing_far",
min_hear_distance = 40,
max_hear_distance = 600,
@ -410,8 +427,10 @@ local gun_default = {
},
},
},
--- `vector` containing the offset from animations, this will be generated if {@consts.ANIMATIONS_OFFSET_AIM}=true
--- `vector` containing the rotation offset from the current frame, this will be factored into the gun's direction if {@consts.ANIMATIONS_OFFSET_AIM}=true
animation_rotation = vector.new(),
--- `vector` containing the translational/positional offset from the current frame
animation_translation = vector.new(),
--- all offsets from @{offsets|gun.offset} of a type added together gun in the same format as a @{offsets|an offset} (that is, five vectors, `gun_axial`, `player_axial`, etc.). Note that if
-- offsets are changed after update, this will not be updated automatically until the next update. update_rotations() must be called to do so.
total_offsets = {
@ -507,7 +526,7 @@ local gun_default = {
--- `bool` wether the gun rotates on it's own axis instead of the player's view (i.e. ironsight misalignments)
HAS_GUN_AXIAL_OFFSETS = true,
--- wether animations create an offset
ANIMATIONS_OFFSET_AIM = false,
ANIMATIONS_OFFSET_AIM = true,
--- whether the idle animation changes or not
LOOP_IDLE_ANIM = false,
--- general gain multiplier for third persons when hearing sounds
@ -520,8 +539,14 @@ local gun_default = {
ARM_RIGHT_BONE = "right_aimpoint",
--- `string`="left_aimpoint", the bone which the left arm aims at to
ARM_LEFT_BONE = "left_aimpoint",
--- `table` version of 4dguns this gun is made for. If left empty it will be assumed it is before 1.3.
VERSION = {1, 2, 0}
},
}
gun_default._properties_unsafe = gun_default.properties
gun_default.properties = leef.class.proxy_table.new(gun_default.properties)
gun_default._consts_unsafe = gun_default.consts
gun_default.consts = leef.class.proxy_table.new(gun_default.consts)
minetest.register_entity("guns4d:gun_entity", {
initial_properties = {

View File

@ -1,49 +0,0 @@
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 = leef.class.new_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,
})

200
classes/Part_handler.lua Normal file
View File

@ -0,0 +1,200 @@
local Part_handler = leef.class.new_class:inherit({})
Guns4d.part_handler = Part_handler
function Part_handler:construct()
assert(self.gun, "no gun object provided")
if self.instance then
local gun = self.gun
local meta = gun.meta
self.player = gun.player
--just a function to warn that there is a cheater...
local warn_cheater = function(p)
core.log("warning", "player: `"..p:get_player_name().."` attempted to access another player's (`"..self.player:get_player_name().."`) gun attachment inventory. This is not possible without cheating!")
end
--currently there is no support for multiple attachments of the same type in a given slot
self.invstring = "guns4d_attachment_inv_"..gun.player:get_player_name()
core.remove_detached_inventory(self.invstring)
local inv = core.create_detached_inventory(self.invstring, {
--allow_move = allow_move,
allow_put = function(_, listname, index, stack, player)
if player == self.player then
local props = gun.properties
if props.inventory.part_slots[listname] and self:can_add(stack, listname) then
return 1
end
return 0
else
warn_cheater(player)
return 0
end
end,
on_put = function(_, listname, index, stack, _)
self:add_attachment(stack, listname, index)
end,
allow_take = function(inv, listname, index, stack, player)
if (player == self.player) then
if self.parts[listname][index] then
return 1
else
return 0
end
else
warn_cheater(player)
return 0
end
end,
on_take = function(_, listname, index, _, _)
self:remove_attachment(index, listname)
end,
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
if player == self.player then
if self.parts[from_list][from_index] and self:can_add(inv:get_stack(from_list, from_index), to_list) then --can be removed
return 1
else
return 0
end
else
warn_cheater(player)
return 0
end
end,
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
self:remove_attachment(from_index, from_list)
self:add_attachment(inv:get_stack(to_list, to_index), to_list, to_index)
end
--allow_take = allow_take
})
self.virtual_inventory = inv
self.handler = self.gun.handler
gun.property_modifiers["part_handler"] = function(props)
for _, slot in pairs(self.parts) do
for _, stack in pairs(slot) do
local mod_def = Guns4d.registered_attachments[stack:get_name()]
if mod_def and mod_def.mod then
mod_def.mod(props)
end
end
end
end
--initialize attachments
if meta:get_string("guns4d_attachments") == "" then
self.parts = {}
for i, partdef in pairs(self.gun.properties.inventory.part_slots) do
--set the size of the virtual inventory slot
inv:set_size(i, partdef.slots or 1)
self.parts[i] = {}
if type(partdef.default)=="string" then
self:add_attachment(partdef.default)
end
end
meta:set_string("guns4d_attachments", core.serialize(self.parts))
else
self.parts = core.deserialize(meta:get_string("guns4d_attachments"))
for slotname, slot in pairs(self.parts) do
--set the size of the virtual inventory slot
inv:set_size(slotname, self.gun.properties.inventory.part_slots[slotname].slots or 1)
for i, stack in pairs(slot) do
slot[i] = ItemStack(stack)
if type(i) == "number" then
inv:set_stack(slotname, i, slot[i])
else
slot[i] = nil
end
end
end
end
self.gun:regenerate_properties()
end
end
--[[
--basically. Done like this to allow for quick lookups of stacks
attachments = {
part_slot = { --part slot as defined in properties.inventory.part_slots
["item_name"] = ItemStack("item_name . . .")
}
}
]]
Guns4d.registered_attachments = {}
function Part_handler.register_attachment(def)
assert(def.itemstring, "itemstring field required")
--assert(def.modifier)
Guns4d.registered_attachments[def.itemstring] = def
end
function Part_handler:update_parts()
local meta = self.gun.meta
local new_meta = table.copy(self.parts)
for _, slot in pairs(new_meta) do
for stackname, stack in pairs(slot) do
slot[stackname] = stack:to_string()
end
end
--print(dump(new_meta))
meta:set_string("guns4d_attachments", core.serialize(new_meta))
self.handler.player:set_wielded_item(self.gun.itemstack)
end
--returns bool indicating success. Attempts to add the attachment.
function Part_handler:add_attachment(itemstack, slotname, index)
assert(self.instance)
itemstack = ItemStack(itemstack)
if self:can_add(itemstack, slotname) then
self.parts[slotname][index]=itemstack
self:update_parts()
return true
else
return false
end
end
--check if it has a part
function Part_handler:has_part(slotname, itemname)
for i, v in pairs(self.parts[slotname]) do
if v and (v:get_name()==itemname) then
return true
end
end
return false
end
--check if it can be added. WARNING: after a change is made, the gun's regenerate_properties must be called
function Part_handler:can_add(itemstack, slotname)
assert(self.instance)
local itemname = itemstack:get_name()
local props = self.gun.properties
--if props.inventory.part_slots[slotname][index] then return false end
--print(slot, dump(self.parts))
if
(not self:has_part(slotname, itemname)) and (props.inventory.part_slots[slotname].allowed)
then
--check if it's allowed, group check required
for i, v in pairs(props.inventory.part_slots[slotname].allowed) do
--print(v, name)
if v==itemname then
return true
end
end
else
return false
end
end
--returns bool indicating success.
function Part_handler:remove_attachment(index, slot)
assert(self.instance)
if self.parts[slot][index] then
self.parts[slot][index] = nil
self:update_parts()
return true
else
return false
end
end
function Part_handler:prepare_deletion()
core.remove_detached_inventory(self.invstring)
end

View File

@ -22,28 +22,27 @@ local physics_system = leef.class.new_class:inherit({
--calculate delta-velocity of a given forcefield
--@tparam int index of the forcefield
--@treturn deltaV of
local rpos = vector.new()
--not going to optimize this because AHHHHHHHHHH its a lot of vector math
function physics_system:update(dt)
for i, v in pairs() do
end
end
function physics_system:calculate_dv(dt, i)
local pos =
local field = self.forcefields[i]
local borderR = field.border_radius
local deadzoneR = field.deadzone_radius
local midlineR = field.midline_radius
local fpos = field.pos
local field_pos = field.pos
local pos = field.target_pos
--rpos.x, rpos.y, rpos.z = pos.x-fpos.x,pos.y-fpos.y,pos.z-fpos.x
rpos = pos-fpos --relative pos
local midline_intersect = rpos:normalize()*midlineR --dir*r is the intersect with midline
local d=(midline_intersect-pos):length() --distance from midline
local e= field.elastic_constant
local f = e*d^(math.abs(e)/e) --force
--local a = f/self.object_weight --acceleration
return a*dt --change in velocity
local dir = (pos-field_pos):normalize() --direction of pos from field
local midline_intersect = dir*midlineR --dir*r is the intersect with midline
local dist = (midline_intersect-pos):length()-deadzoneR --distance from midline's deadzone
if dist < 0 then return vector.new() end
-- if dist > 0 then we pull it to the radius
local e = field.elastic_constant
local ft = e*dist^(math.abs(e)/e) * vector.dot(dir, pos) --force applied to translation
local a = ft/self.object_weight --acceleration
return dir*(a*dt)
end

View File

@ -39,12 +39,15 @@ Guns4d.player_model_handler = {
},
new_bones = { --currently only supports empty bones. Sets at identity rotation, position 0, and parentless
"guns4d_gun_bone",
"guns4d_reflector_bone"
},
bone_aliases = { --names of bones used by the model handler and other parts of guns4d.
arm_right = "guns4d_arm_right", --this is a needed alias for hipfire position
arm_left = "guns4d_arm_left",
head = "guns4d_head",
gun = "guns4d_gun_bone", --another needed alias
reflector = "guns4d_reflector_bone"
},
still_frame = 0, --the frame to take bone offsets from. This system has to be improved in the future (to allow better animation support)- it works for now though.
auto_generate = true,
@ -80,74 +83,6 @@ end
end]]
--we store the read file so it can be reused in the constructor if needed.
local model_buffer
local modpath = minetest.get_modpath("guns4d")
function player_model:custom_b3d_generation_parameters(b3d)
--empty for now, this is for model customizations.
return b3d
end
function player_model:replace_b3d_bone(b3d)
end
--generates a new guns4d model bases off of the `new_bones` and `bone_overrides` parameters if one does not already exist.
function player_model:generate_b3d_model(name)
assert(self and name, "error while generating a b3d model. Name not provided or not called as a method.")
--generate a new model
local filename = string.sub(name, 1, -5).."_guns4d_temp.b3d"
local new_path = self.output_path..filename
--buffer and modify the model
model_buffer = leef.b3d_reader.read_model(name)
local b3d = model_buffer
local replaced = {}
--add bone... forgot i made this so simple by adding node_paths
for _, node in pairs(b3d.node_paths) do
if self.override_bones[node.name] then
replaced[node.name] = true
--change the name
node.name = self.override_bones[node.name]
--unset rotation because it breaks shit
local rot = node.rotation
for i, v in pairs(node.keys) do
v.rotation = rot
end
--node.rotation = {0,0,0,1}
end
end
--check bones were replaced to avoid errors.
for i, v in pairs(self.override_bones) do
if (not replaced[i]) and i~="__overfill" then
error("bone '"..i.."' not replaced with it's guns4d counterpart, bone was not found. Check bone name")
end
end
for i, v in pairs(self.new_bones) do
table.insert(b3d.node.children, {
name = v,
position = {0,0,0},
scale = {1/self.scale,1/self.scale,1/self.scale},
rotation = {0,0,0,1},
children = {},
bone = {} --empty works?
})
end
--call custom generation parameters...
b3d=self:custom_b3d_generation_parameters(b3d)
--write temp model
local writefile = io.open(new_path, "w+b")
leef.b3d_writer.write_model_to_file(b3d, writefile)
writefile:close()
--send to player media paths
minetest.after(0, function()
assert(
minetest.dynamic_add_media({filepath = new_path}, function()end),
"failed sending media"
)
end)
leef.paths.media_paths[filename] = new_path
leef.paths.modname_by_media[filename] = "guns4d"
return filename
end
-- main update function
function player_model:update(dt)
@ -176,12 +111,29 @@ function player_model:update_aiming(dt)
local ip = Guns4d.math.smooth_ratio(handler.control_handler.ads_location or 0)
local ip_inv = 1-ip
local pos = self.gun_bone_location --reuse allocated table
local xr,yr,zr = gun:get_rotation_transform(ttransform, nil, nil, nil, nil, nil, nil, 0, nil,nil,nil):get_rot_irrlicht_bone()
pos.x = ( (player_trans.x*10) + ((gun and gun.properties.ads.horizontal_offset*10) or 0 ))/vs.x
pos.y = ( (player_trans.y*10) + (pprops.eye_height*10) )/vs.y
pos.z = ( (player_trans.z*10) )/vs.z
player:set_bone_override(self.bone_aliases.reflector,
{
position = {
vec={x=pos.x, y=pos.y, z=pos.z},
interpolation=ip_time2,
absolute = true
},
rotation = {
vec={x=xr,y=yr,z=zr},
interpolation=ip_time,
absolute = true
}
})
--hip pos is already relative to local scale
pos.x = (hip_pos.x*10*ip_inv)+( (player_trans.x*10) + ((gun and gun.properties.ads.horizontal_offset*10*ip) or 0 ))/vs.x
pos.y = (hip_pos.y*10*ip_inv)+( (player_trans.y*10) + (pprops.eye_height*10*ip) )/vs.y
pos.z = (hip_pos.z*10*ip_inv)+( (player_trans.z*10) )/vs.z
local xr,yr,zr = gun:get_rotation_transform(ttransform, 0,0,0, nil,nil, nil,0, 0,0,0):get_rot_irrlicht_bone()
xr,yr,zr = gun:get_rotation_transform(ttransform, 0,0,0, nil,nil, nil,0, 0,0,0):get_rot_irrlicht_bone()
player:set_bone_override(self.bone_aliases.gun,
{
position = {
@ -270,6 +222,76 @@ function player_model:prepare_deletion()
handler:set_properties(properties)
end
--todo: add value for "still_frame" (the frame to take samples from in case 0, 0 is not still.)
local model_buffer
local modpath = minetest.get_modpath("guns4d")
function player_model:custom_b3d_generation_parameters(b3d)
--empty for now, this is for model customizations.
return b3d
end
--generates a new guns4d model bases off of the `new_bones` and `bone_overrides` parameters if one does not already exist.
function player_model:generate_b3d_model(name)
assert(self and name, "error while generating a b3d model. Name not provided or not called as a method.")
--generate a new model
local filename = string.sub(name, 1, -5).."_guns4d_temp.b3d"
local new_path = self.output_path..filename
--buffer and modify the model
model_buffer = leef.b3d_reader.read_model(name)
local b3d = model_buffer
local replaced = {}
--add bone... forgot i made this so simple by adding node_paths
for _, node in pairs(b3d.node_paths) do
if self.override_bones[node.name] then
replaced[node.name] = true
--change the name
node.name = self.override_bones[node.name]
--unset rotation because it breaks shit
local rot = node.rotation
for i, v in pairs(node.keys) do
v.rotation = rot
end
--node.rotation = {0,0,0,1}
end
end
--check bones were replaced to avoid errors.
for i, v in pairs(self.override_bones) do
if (not replaced[i]) and i~="__replace_old_table" then
error("bone '"..i.."' not replaced with it's guns4d counterpart, bone was not found. Check bone name")
end
end
for i, v in pairs(self.new_bones) do
table.insert(b3d.node.children, {
name = v,
position = {0,0,0},
scale = {1/self.scale,1/self.scale,1/self.scale},
rotation = {0,0,0,1},
children = {},
bone = {} --empty works?
})
end
--call custom generation parameters...
b3d=self:custom_b3d_generation_parameters(b3d)
--write temp model
local writefile = io.open(new_path, "w+b")
leef.b3d_writer.write_model_to_file(b3d, writefile)
writefile:close()
--send to player media paths
minetest.after(0, function()
assert(
minetest.dynamic_add_media({filepath = new_path}, function()end),
"failed sending media"
)
end)
leef.paths.media_paths[filename] = new_path
leef.paths.modname_by_media[filename] = "guns4d"
return filename
end
---@diagnostic disable-next-line: duplicate-set-field
function player_model.construct(def)
if def.instance then
@ -320,7 +342,6 @@ function player_model.construct(def)
--[[all of the compatible_meshes should be identical in terms of guns4d specific bones and offsets (arms, head).
Otherwise a new handler should be different. With new compatibilities]]
for i, v in pairs(def.bone_aliases) do
print(def.bone_aliases[i])
local node = leef.b3d_nodes.get_node_by_name(b3d_table, v, true)
assert(node, "player model handler: no node found by the name of \""..v.."\" check that it is the correct value, or that it has been correctly overriden to use that name.")
local transform, _ = leef.b3d_nodes.get_node_global_transform(node, def.still_frame)

View File

@ -0,0 +1,77 @@
local Reflector_sight = {
texture = "holographic_reflection.png",
scale = 1,
offset = 1,
deviation_tolerance = {
min = .01,
max = .05,
depth = .1
},
}
local blank = "blank.png"
core.register_entity("guns4d:reflector_sight", {
initial_properties = {
textures = {
blank,
blank,
blank,
blank,
blank,
Reflector_sight.texture,
},
glow = 14,
visual = "cube",
visual_size = {x=.1, y=.1, z=.1},
physical = false,
shaded = false
}
})
Reflector_sight.on_construct = function(self)
self:initialize_entity()
end
local m4 = leef.math.mat4.new()
function Reflector_sight:initialize_entity()
local obj = minetest.add_entity(self.gun.player:get_pos(), "guns4d:reflector_sight")
obj:set_properties({
textures = {
blank,
blank,
blank,
blank,
blank,
self.texture
},
visual_size = {x=self.scale/10, y=self.scale/10, z=self.scale/10},
use_texture_alpha = true
})
self.entity = obj
obj:set_attach(self.gun.player, self.gun.handler.player_model_handler.bone_aliases.reflector, nil, nil, true)
end
function Reflector_sight:update(dt)
if self.entity then
self.entity:set_attach(self.gun.player, self.gun.handler.player_model_handler.bone_aliases.reflector, {x=0,y=0,z=self.offset*10}, nil, true)
local v1 = leef.math.mat4.mul_vec4({}, self.gun:get_rotation_transform(m4, nil, nil, nil, nil, nil, nil, nil, nil,nil,nil), {0,0,self.offset,0})
local v2 = leef.math.mat4.mul_vec4({}, self.gun:get_rotation_transform(m4, 0, 0, 0, nil, nil, nil, nil, 0,0,0), {0,0,self.offset,0})
--[[local dist = vector.distance({x=v1[1], y=v1[2], z=v1[3]}, {x=v2[1], y=v2[2], z=v2[3]})
minetest.chat_send_all(dist)
self.entity:set_properties({
textures = {
blank,
blank,
blank,
blank,
blank,
self.texture .. "^[opacity:"..255-math.ceil(255*((dist-self.deviation_tolerance.min)/self.deviation_tolerance.max))
},
})]]
else
self:initialize_entity()
end
end
function Reflector_sight:prepare_deletion()
if self.entity then
self.entity:remove()
end
end
Guns4d.Reflector_sight = leef.class.new_class:inherit(Reflector_sight)

View File

@ -69,6 +69,8 @@ function Sprite_scope:update()
local image = self.images[i]
local projection_pos=image.projection_pos
local relative_pos
vec4_dir = mat4.mul_vec4(vec4_dir, gun:get_rotation_transform(transform,nil,nil,nil, nil,nil, 0,0), vec4_forward)
if projection_pos then
vec3_in.x = projection_pos.x/10
vec3_in.y = projection_pos.y/10
@ -78,21 +80,19 @@ function Sprite_scope:update()
relative_pos.x = relative_pos.x - (player_trans.x + (gun and gun.properties.ads.horizontal_offset or 0))
relative_pos.y = relative_pos.y - hip_trans.y - (player_trans.y + pprops.eye_height)
relative_pos.z = relative_pos.z - (player_trans.z)
gun:get_rotation_transform(transform,nil,nil,nil, nil,nil, 0,0)
else
local r = gun.total_offsets.gun_axial
local a = gun.animation_rotation
vec4_dir = mat4.mul_vec4(vec4_dir, gun:get_rotation_transform(transform,nil,nil,nil, nil,nil, 0,0), vec4_forward)
relative_pos = vec3_in
relative_pos.x = vec4_dir[1]
relative_pos.y = vec4_dir[2]
relative_pos.z = vec4_dir[3]
--relative_pos = gun:get_dir(true)
end
local hud_pos = Guns4d.math.rltv_point_to_hud(relative_pos, 80/self.magnification, ratio)
--print(i, hud_pos.x, hud_pos.y)
self.player:hud_change(v, "position", {x=hud_pos.x+.5, y=hud_pos.y+.5})
local z = relative_pos.z
self.player:hud_change(v, "scale", {x=(image.scale.x/z)*1-vec4_dir[2], y=(image.scale.y/z)*1-vec4_dir[1]})
end
elseif self.fov_set then
self.fov_set = false

View File

@ -300,7 +300,8 @@ Guns4d.default_controls.reload = {
if type(next_state.sounds) == "table" then
sounds = Guns4d.table.deep_copy(props.reload[next_state_index].sounds)
elseif type(next_state.sounds) == "string" then
sounds = Guns4d.table.deep_copy(assert(props.sounds[next_state.sounds], "no sound by the name of "..next_state.sounds))
assert(props.sounds[next_state.sounds], "no sound by the name of "..next_state.sounds)
sounds = Guns4d.table.deep_copy(props.sounds[next_state.sounds])
end
sounds.pos = gun.pos
data.played_sounds = {gun:play_sounds(sounds)}

View File

@ -297,8 +297,8 @@ offsets are changed after update, this will not be updated automatically until t
</td>
</tr>
<tr>
<td class="name"><var id="lvl1_fields.properties.attachment_handler">attachment_handler</var><a class="permalink" href="#lvl1_fields.properties.attachment_handler" title="Permalink to this definition"></a></td>
<td class="doc"><p><code>Attachment_handler</code> attachment_handler class to use. Default is <code>Guns4d.attachment_handler</code></p>
<td class="name"><var id="lvl1_fields.properties.part_handler">Part_handler</var><a class="permalink" href="#lvl1_fields.properties.part_handler" title="Permalink to this definition"></a></td>
<td class="doc"><p><code>Part_handler</code> Part_handler class to use. Default is <code>Guns4d.part_handler</code></p>
</td>
</tr>
<tr>

View File

@ -48,7 +48,7 @@ var docs = [
{path:"class/gun.html#lvl1_fields.properties.ammo", type:"field", title:"lvl1_fields.properties.ammo", text:"table defines what ammo the gun uses"},
{path:"class/gun.html#lvl1_fields.properties.visuals", type:"field", title:"lvl1_fields.properties.visuals", text:"table defines visual attributes of the gun"},
{path:"class/gun.html#lvl1_fields.properties.ammo_handler", type:"field", title:"lvl1_fields.properties.ammo_handler", text:"Ammo_handler the class (based on) ammo_handler to create an instance of and use. Default is Guns4d.ammo_handler"},
{path:"class/gun.html#lvl1_fields.properties.attachment_handler", type:"field", title:"lvl1_fields.properties.attachment_handler", text:"Attachment_handler attachment_handler class to use. Default is Guns4d.attachment_handler"},
{path:"class/gun.html#lvl1_fields.properties.part_handler", type:"field", title:"lvl1_fields.properties.part_handler", text:"Part_handler Part_handler class to use. Default is Guns4d.part_handler"},
{path:"class/gun.html#lvl1_fields.properties.sprite_scope", type:"field", title:"lvl1_fields.properties.sprite_scope", text:"Sprite_scope sprite scope class to use"},
{path:"class/gun.html#lvl1_fields.properties.crosshair", type:"field", title:"lvl1_fields.properties.crosshair", text:"Dynamic_crosshair crosshair class to use"},
{path:"class/gun.html#lvl1_fields.properties.initial_vertical_rotation", type:"field", title:"lvl1_fields.properties.initial_vertical_rotation", text:"starting vertical rotation of the gun"},

View File

@ -2,7 +2,8 @@ local Vec = vector
Guns4d = {
players = {},
handler_by_ObjRef = {},
gun_by_ObjRef = {} --used for getting the gun object by the ObjRef of the gun
gun_by_ObjRef = {}, --used for getting the gun object by the ObjRef of the gun
version = {1, 3, 0}
}
--default config values, config will be added soon:tm:
Guns4d.config = {
@ -70,8 +71,9 @@ dofile(path.."/Bullet_hole.lua")
dofile(path.."/Bullet_ray.lua")
dofile(path.."/Control_handler.lua")
dofile(path.."/Ammo_handler.lua")
dofile(path.."/Attachment_handler.lua")
dofile(path.."/Part_handler.lua")
dofile(path.."/Sprite_scope.lua")
dofile(path.."/Reflector_sight.lua")
dofile(path.."/Dynamic_crosshair.lua")
dofile(path.."/Gun.lua") --> loads /classes/gun_construct.lua
dofile(path.."/Player_model_handler.lua")
@ -143,6 +145,12 @@ minetest.register_chatcommand("ammoinf", {
})
--player handling
setmetatable(Guns4d.handler_by_ObjRef, {
__mode = "kv"
})
setmetatable(Guns4d.gun_by_ObjRef, {
__mode = "kv"
})
local player_handler = Guns4d.player_handler
local objref_mtable
minetest.register_on_joinplayer(function(player)
@ -199,21 +207,9 @@ minetest.register_on_joinplayer(function(player)
local data = gun.animation_data
data.runtime = 0
data.fps = frame_speed or 15
data.loop = frame_loop
--[[if frame_loop == nil then --still have no idea what nutjob made the default true >:(
frame_loop = false
end
--so... minetest is stupid, and so it won't let me set something to the same animation twice (utterly fucking brilliant).
--This means I literally need to flip flop between +1 frames
--minetest.chat_send_all(dump(frame_range))
if (data.frames.x == frame_range.x and data.frames.y == frame_range.y) and not (frame_range.x==frame_range.y) then
--oh yeah, and it only accepts whole frames... because of course.
frame_range.x = frame_range.x
--minetest.chat_send_all("+1")
end]]
--frame_blend = 25
--minetest.chat_send_all(dump(frame_range))
data.frames = frame_range
--dont use the frame_range table because it's parent could get GCed if it's a metatable (proxy table issue.)
data.frames.x = frame_range.x
data.frames.y = frame_range.y
data.current_frame = data.frames.x
end
return old_set_animation(self, frame_range, frame_speed, frame_blend, frame_loop, ...)

View File

@ -85,107 +85,16 @@ minetest.register_chatcommand("guns4d_guide", {
Guns4d.show_guide(player,1)
end
})
local function lstdmn(h,w)
return {x=w+((w-1)*.125), y=h+((h-1)*.125)}
end
--[[local allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
end]]
local allow_put = function(inv, listname, index, stack, player, gun)
local props = gun.properties
local atthan = gun.attachment_handler
if props.inventory.attachment_slots[listname] and atthan:can_add(stack, listname) then
return 1
end
return 0
end
--[[local allow_take = function(inv, listname, index, stack, player, gun)
end]]
local on_put = function(inv, listname, index, stack, player, gun)
gun.attachment_handler:add_attachment(stack, listname)
end
local on_take = function(inv, listname, index, stack, player, gun)
gun.attachment_handler:remove_attachment(stack, listname)
end
function Guns4d.show_gun_menu(gun)
local props = gun.properties
local player = gun.player
local pname = player:get_player_name()
local inv = minetest.get_inventory({type="player", name=pname})
local window = minetest.get_player_window_information(pname)
local listname = Guns4d.config.inventory_listname
local form_dimensions = {x=20,y=15}
local inv_height=4+((4-1)*.125)
local hotbar_length = player:hud_get_hotbar_itemcount()
local form = "\
formspec_version[7]\
size[".. form_dimensions.x ..",".. form_dimensions.y .."]"
local hotbar_height = math.ceil(hotbar_length/8)
form = form.."\
scroll_container[.25,"..(form_dimensions.y)-inv_height-1.25 ..";10,5;player_inventory;vertical;.05]\
list[current_player;"..listname..";0,0;"..hotbar_length..","..hotbar_height..";]\
list[current_player;"..listname..";0,1.5;8,3;"..hotbar_length.."]\
scroll_container_end[]\
"
if math.ceil(inv:get_size("main")/8) > 4 then
local h = math.ceil(inv:get_size("main")/8)
form=form.."\
scrollbaroptions[max="..h+((h-1)*.125).."]\
scrollbar[10.25,"..(form_dimensions.y)-inv_height-1.25 ..";.5,5;vertical;player_inventory;0]\
"
end
--display gun preview
local len = math.abs(gun.model_bounding_box[3]-gun.model_bounding_box[6])/props.visuals.scale
local hei = math.abs(gun.model_bounding_box[2]-gun.model_bounding_box[5])/props.visuals.scale
local offsets = {x=(-gun.model_bounding_box[6]/props.visuals.scale)-(len/2), y=(gun.model_bounding_box[5]/props.visuals.scale)+(hei/2)}
local meter_scale = 15
local image_scale = meter_scale*(props.inventory.render_size or 1)
local gun_gui_offset = {x=0,y=-2.5}
form = form.."container["..((form_dimensions.x-image_scale)/2)+gun_gui_offset.x.. ","..((form_dimensions.y-image_scale)/2)+gun_gui_offset.y.."]"
if props.inventory.render_image then
form = form.."image["
..(offsets.x*meter_scale) ..","
..(offsets.y*meter_scale) ..";"
..image_scale..","
..image_scale..";"
..props.inventory.render_image.."]"
end
local attachment_inv = minetest.create_detached_inventory("guns4d_inv_"..pname, {
--allow_move = allow_move,
allow_put = function(inv, putlistname, index, stack, player)
return allow_put(inv, putlistname, index, stack, player, gun)
end,
on_put = function(inv, putlistname, index, stack, player)
return on_put(inv, putlistname, index, stack, player, gun)
end,
on_take = function(inv, putlistname, index, stack, player)
return on_take(inv, putlistname, index, stack, player, gun)
end
--allow_take = allow_take
})
if props.inventory.attachment_slots then
for i, attachment in pairs(props.inventory.attachment_slots) do
attachment_inv:set_size(i, attachment.slots or 1)
form = form.."label["..(image_scale/2)+(attachment.formspec_offset.x or 0)-.75 ..","..(image_scale/2)+(-attachment.formspec_offset.y or 0)-.2 ..";"..(attachment.description or i).."]"
--list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]
local width = attachment.slots or 1
width = width+((width-1)*.125)
form = form.."list[detached:guns4d_inv_"..pname..";"..i..";"..(image_scale/2)+(attachment.formspec_offset.x or 0)-(width/2)..","..(image_scale/2)+(-attachment.formspec_offset.y or 0)..";3,5;]"
end
end
form = form.."container_end[]"
minetest.show_formspec(gun.handler.player:get_player_name(), "guns4d:inventory", form)
end
minetest.register_chatcommand("guns4d_inv", {
description = "Show the gun menu.",
func = function(pname, arg)
local gun = Guns4d.players[pname].gun
if gun then
Guns4d.show_gun_menu(gun)
gun:open_inventory_menu()
else
minetest.chat_send_player(pname, "cannot show the inventory menu for a gun which is not help")
end

View File

@ -295,40 +295,57 @@ end
return "{"..str..string.sub(initial_string, 1, -5).."}"
end]]
--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 Guns4d.table.fill(tbl, replacement, preserve_reference, indexed_tables)
--these need to be documented flags:
--__no_copy --the replacing (table) will not be copied, and the field containing it will be a reference to the orginal table found in replacement
--__replace_old_table = true --the replacing table declares that the old table should be replaced with the new one
--__replace_only = true --the original table declares that it should be replaced with the new one
--[field] = "__redact_field" --the field will be nil even if it existed in the old table
--replace fields (and fill sub-tables) in `tbl` with elements in `replacement`. Recursively iterates all sub-tables. use property __replace_old_table=true for subtables that don't want to be overfilled.
function Guns4d.table.fill(to_fill, replacement, overwrite_original, indexed_tables)
if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing
local new_table = tbl
if not preserve_reference then
new_table = Guns4d.table.deep_copy(tbl)
local fill_table = to_fill
if not overwrite_original then
fill_table = Guns4d.table.deep_copy(to_fill)
end
for i, v in pairs(replacement) do
if new_table[i] then
if fill_table[i] then --check if it actually exists in the table we're filling
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
if (type(fill_table[i]) == "table") then
--only need to check if it's been indexed if we're iterating downward into it where it could loop.
if (not indexed_tables[v]) and (not replacement[i].__replace_old_table) and (not fill_table[i].__replace_only) then
indexed_tables[v] = true
new_table[i] = Guns4d.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] = Guns4d.table.deep_copy(replacement[i])
end
end
--get the original table and not the copy otherwise
fill_table[i] = Guns4d.table.fill(fill_table[i], replacement[i], true, indexed_tables)
elseif not replacement[i].__no_copy then
new_table[i] = Guns4d.table.deep_copy(replacement[i])
fill_table[i] = Guns4d.table.deep_copy(replacement[i])
end
elseif not replacement[i].__no_copy then --if it's allowed to be copyed, since there's no table to fill in, we just copy it.
fill_table[i] = Guns4d.table.deep_copy(replacement[i])
else
new_table[i] = replacement[i]
fill_table[i] = replacement[i]
end
new_table[i].__overfill = nil
--cant modify the original table
if not replacement[i].__no_copy then
fill_table[i].__no_copy = nil
fill_table[i].__replace_old_table = nil
fill_table[i].__replace_only = nil
end
elseif replacement[i] ~= "__redact_field" then
fill_table[i] = replacement[i]
end
else
new_table[i] = replacement[i]
end
else
new_table[i] = replacement[i]
fill_table[i] = replacement[i]
end
end
return new_table
return fill_table
end
--for class based OOP, ensure values containing a table in btbl are tables in a_tbl- instantiate, but do not fill.
--[[function table.instantiate_struct(tbl, btbl, indexed_tables)

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B