added framework for a gun menu and inventories, created attachment manager
This commit is contained in:
parent
0a6c07cce1
commit
e5906fee9e
13
attachments.lua
Normal file
13
attachments.lua
Normal file
@ -0,0 +1,13 @@
|
||||
local attachment = {
|
||||
attached_bone = "gun",
|
||||
|
||||
}
|
||||
function attachment:construct()
|
||||
if self.instance then
|
||||
assert(self.gun, "attachment has no gun")
|
||||
end
|
||||
end
|
||||
function attachment:update_entity()
|
||||
self.entity =
|
||||
end
|
||||
Guns4d.gun_attachment = mtul.class.new_class:inherit(attachment)
|
@ -1,4 +1,4 @@
|
||||
Ammo_handler = mtul.class.new_class:inherit({
|
||||
local Ammo_handler = mtul.class.new_class:inherit({
|
||||
name = "Ammo_handler",
|
||||
construct = function(def)
|
||||
if def.instance then
|
||||
@ -9,8 +9,9 @@ Ammo_handler = mtul.class.new_class:inherit({
|
||||
local gun = def.gun
|
||||
def.ammo = {}
|
||||
if gun.properties.ammo then
|
||||
--this is pretty inefficient it's been built on. Refactor later maybe.
|
||||
local spawn_with = meta:get_int("guns4d_spawn_with_ammo")
|
||||
if (meta:get_string("guns4d_loaded_bullets") == "") and ((spawn_with > 0) or (Guns4d.config.interpret_initial_wear_as_ammo))then
|
||||
if (meta:get_string("guns4d_loaded_bullets") == "") and ((spawn_with > 0) or (Guns4d.config.interpret_initial_wear_as_ammo)) then
|
||||
local bullets = (spawn_with > 0 and spawn_with) or (1-(def.gun.itemstack:get_wear()/65535))
|
||||
if gun.properties.ammo.magazine_only then
|
||||
local magname = gun.properties.ammo.accepted_magazines[1]
|
||||
@ -47,6 +48,7 @@ Ammo_handler = mtul.class.new_class:inherit({
|
||||
end
|
||||
end
|
||||
})
|
||||
Guns4d.ammo_handler = Ammo_handler
|
||||
--spend the round, return false if impossible.
|
||||
--updates all properties based on the ammo table, bullets string can be passed directly to avoid duplication (when needed)
|
||||
function Ammo_handler:update_meta(bullets)
|
||||
@ -74,7 +76,7 @@ function Ammo_handler:get_magazine_bone_info()
|
||||
local pos1 = vector.new(mtul.b3d_nodes.get_node_global_position(nil, node, true, math.floor(gun.animation_data.current_frame)))
|
||||
local pos2 = vector.new(mtul.b3d_nodes.get_node_global_position(nil, node, true, gun.animation_data.current_frame))
|
||||
local vel = (pos2-pos1)*((gun.animation_data.current_frame-math.floor(gun.animation_data.current_frame))/gun.animation_data.fps)+self.gun.player:get_velocity()
|
||||
local pos = self.gun:get_pos(pos2/10)+self.gun.handler:get_pos()
|
||||
local pos = self.gun:get_pos(pos2*gun.properties.visual_scale)+self.gun.handler:get_pos()
|
||||
--[[so I tried to get the rotation before, and it actually turns out that was... insanely difficult? Why? I don't know, the rotation behavior was beyond unexpected, I tried implementing it with quats and
|
||||
matrices and neither worked. I'm done, I've spent countless hours on this, and its fuckin stupid to spend a SECOND more on this pointless shit. It wouldnt even look that much better!]]
|
||||
return pos, vel
|
||||
@ -314,18 +316,3 @@ function Ammo_handler:unload_all(to_ground)
|
||||
self.ammo.loaded_bullets = {}
|
||||
self:update_meta()
|
||||
end
|
||||
function Ammo_handler:load_magless()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
end
|
||||
function Ammo_handler:unload_magless()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
end
|
||||
function Ammo_handler:load_fractional()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
end
|
||||
function Ammo_handler:unload_fractional()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
end
|
||||
function Ammo_handler:unload_chamber()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
end
|
94
classes/Attachment_handler.lua
Normal file
94
classes/Attachment_handler.lua
Normal file
@ -0,0 +1,94 @@
|
||||
--will have to merge with ammo_handler eventually for coherency.
|
||||
local attachment_handler = mtul.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
|
@ -50,7 +50,7 @@ function controls:update(dt)
|
||||
local gun = self.gun
|
||||
if not (gun.rechamber_time > 0 and gun.ammo_handler.ammo.next_bullet == "empty") then --check if the gun is being charged.
|
||||
for i, control in pairs(self:get_actions()) do
|
||||
if not (i=="on_use") and not (i=="on_secondary_use") then
|
||||
if (i~="on_use") and (i~="on_secondary_use") then
|
||||
local def = control
|
||||
local data = control.data
|
||||
local conditions_met = true
|
||||
@ -105,7 +105,7 @@ function controls:update(dt)
|
||||
end
|
||||
end
|
||||
for i, tbl in pairs(call_queue) do
|
||||
tbl.control.func(tbl.active, tbl.interrupt, tbl.data, busy_list, gun, self.handler)
|
||||
tbl.control.func(self, tbl.active, tbl.interrupt, tbl.data, busy_list, gun, self.handler)
|
||||
end
|
||||
self.busy_list = {}
|
||||
elseif self.busy_list then
|
||||
@ -116,42 +116,47 @@ function controls:update(dt)
|
||||
--if aiming, then increase ADS location
|
||||
self.ads_location = Guns4d.math.clamp(self.ads_location + (dt/gun.properties.ads.aim_time), 0, 1)
|
||||
elseif (not self.ads) and (self.ads_location>0) then
|
||||
local divisor = gun.properties.ads.aim_time/gun.consts.AIM_OUT_AIM_IN_SPEED_RATIO
|
||||
local divisor = gun.properties.ads.aim_time/Guns4d.config.aim_out_multiplier
|
||||
self.ads_location = Guns4d.math.clamp(self.ads_location - (dt/divisor), 0, 1)
|
||||
end
|
||||
end
|
||||
|
||||
--builtin overrides for the item
|
||||
function controls:on_use(itemstack, pointed_thing)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local actions = self:get_actions()
|
||||
if actions.on_use then
|
||||
actions.on_use(itemstack, self.handler, pointed_thing)
|
||||
actions.on_use(self, itemstack, self.handler, pointed_thing, self.busy_list)
|
||||
end
|
||||
end
|
||||
function controls:on_drop(itemstack, pointed_thing, pos)
|
||||
local actions = self:get_actions()
|
||||
if actions.on_drop then
|
||||
return actions.on_use(itemstack, self.handler, pos)
|
||||
return actions.on_use(self, itemstack, self.handler, pos, self.busy_list)
|
||||
end
|
||||
end
|
||||
function controls:on_secondary_use(itemstack, pointed_thing)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local actions = self:get_actions()
|
||||
if actions.on_secondary_use then
|
||||
actions.on_secondary_use(itemstack, self.handler, pointed_thing)
|
||||
actions.on_secondary_use(self, itemstack, self.handler, pointed_thing, self.busy_list)
|
||||
end
|
||||
end
|
||||
|
||||
--touchscreen mode, work in progress.
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function controls:toggle_touchscreen_mode(active)
|
||||
if active~=nil then self.touchscreen=active else self.touchscreen = not self.touchscreen end
|
||||
self.handler.touchscreen = self.touchscreen
|
||||
for i, action in pairs((self.touchscreen and self.actions_pc) or self.actions_touch) do
|
||||
if not (i=="on_use") and not (i=="on_secondary_use") then
|
||||
if (i~="on_use") and (i~="on_secondary_use") then
|
||||
action.timer = action.timer or 0
|
||||
action.data = nil --no need to store excess data
|
||||
end
|
||||
end
|
||||
for i, action in pairs((self.touchscreen and self.actions_touch) or self.actions_pc) do
|
||||
if not (i=="on_use") and not (i=="on_secondary_use") then
|
||||
if(i~="on_use") and (i~="on_secondary_use") then
|
||||
action.timer = action.timer or 0
|
||||
action.data = {
|
||||
timer = action.timer,
|
||||
|
@ -92,13 +92,18 @@ function gun_default:construct_instance()
|
||||
|
||||
--unavoidable table instancing
|
||||
self.properties = Guns4d.table.fill(self.base_class.properties, self.properties)
|
||||
self.particle_spawners = {} --mtul.class.new_class only shallow copies. So tables will not change, and thus some need to be initialized.
|
||||
self.property_modifiers = {}
|
||||
self.particle_spawners = {}
|
||||
self.property_modifiers = {}
|
||||
|
||||
initialize_animation(self)
|
||||
initialize_physics(self)
|
||||
|
||||
--properties have been assigned, create necessary objects TODO: completely change this system for selfining them.
|
||||
if self.properties.inventory.attachment_slots then
|
||||
self.attachment_handler = self.properties.attachment_handler:new({
|
||||
gun = self
|
||||
})
|
||||
end
|
||||
if self.properties.sprite_scope then
|
||||
self.sprite_scope = self.properties.sprite_scope:new({
|
||||
gun = self
|
||||
@ -152,7 +157,7 @@ local function validate_controls(props)
|
||||
end
|
||||
end
|
||||
local function initialize_b3d_animation_data(self, props)
|
||||
self.b3d_model = mtul.b3d_reader.read_model(props.visuals.mesh, true)
|
||||
self.b3d_model = mtul.b3d_reader.read_model(props.visuals.mesh)
|
||||
self.b3d_model.global_frames = {
|
||||
arm_right = {}, --the aim position of the right arm
|
||||
arm_left = {}, --the aim position of the left arm
|
||||
@ -167,18 +172,19 @@ local function initialize_b3d_animation_data(self, props)
|
||||
for target_frame = 0, self.b3d_model.node.animation.frames+1, self.consts.KEYFRAME_SAMPLE_PRECISION do
|
||||
--we need to check that the bone exists first.
|
||||
if left then
|
||||
table.insert(self.b3d_model.global_frames.arm_left, vector.new(mtul.b3d_nodes.get_node_global_position(self.b3d_model, left, nil, target_frame))/10)
|
||||
table.insert(self.b3d_model.global_frames.arm_left, vector.new(mtul.b3d_nodes.get_node_global_position(self.b3d_model, left, nil, target_frame))*props.visuals.scale)
|
||||
else
|
||||
self.b3d_model.global_frames.arm_left = nil
|
||||
end
|
||||
|
||||
if right then
|
||||
table.insert(self.b3d_model.global_frames.arm_right, vector.new(mtul.b3d_nodes.get_node_global_position(self.b3d_model, right, nil, target_frame))/10)
|
||||
table.insert(self.b3d_model.global_frames.arm_right, vector.new(mtul.b3d_nodes.get_node_global_position(self.b3d_model, right, nil, target_frame))*props.visuals.scale)
|
||||
else
|
||||
self.b3d_model.global_frames.arm_right = nil
|
||||
end
|
||||
|
||||
if main then
|
||||
--ATTENTION: this is broken, roll is somehow translating to yaw. How? fuck if I know, but I will have to fix this eventually.
|
||||
--use -1 as it does not exist and thus will always go to the default resting pose
|
||||
--we compose it by the inverse because we need to get the global CHANGE in rotation for the animation rotation offset. I really need to comment more often
|
||||
local newvec = (mtul.b3d_nodes.get_node_rotation(self.b3d_model, main, nil, -1):inverse())*mtul.b3d_nodes.get_node_rotation(self.b3d_model, main, nil, target_frame)
|
||||
@ -187,14 +193,38 @@ local function initialize_b3d_animation_data(self, props)
|
||||
end
|
||||
end
|
||||
|
||||
--[[if main then
|
||||
local quat = mtul.math.quat.new(main.keys[1].rotation)
|
||||
print(dump(main.keys[1]), vector.new(quat:to_euler_angles_unpack(quat)))
|
||||
local verts = {}
|
||||
self.bones = {}
|
||||
--iterate all nodes, check for meshes.
|
||||
for i, v in pairs(self.b3d_model.node_paths) do
|
||||
if v.mesh then
|
||||
--if there's a mesh present transform it's verts into global coordinate system, add add them to them to a big list.
|
||||
local transform, _ = mtul.b3d_nodes.get_node_global_transform(v, self.properties.visuals.animations.loaded.x, "transform")
|
||||
for _, vert in ipairs(v.mesh.vertices) do
|
||||
vert.pos[4]=1
|
||||
table.insert(verts, transform*vert.pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
for i, v in pairs(self.b3d_model.global_frames.rotation) do
|
||||
print(i, dump(vector.new(v:to_euler_angles_unpack())*180/math.pi))
|
||||
end]]
|
||||
--print()
|
||||
local high_points = {0,0,0,0,0,0}
|
||||
for _, v in pairs(verts) do
|
||||
for i = 1,3 do
|
||||
if high_points[i+3] > v[i] then
|
||||
high_points[i+3]=v[i]
|
||||
end
|
||||
if high_points[i] < v[i] then
|
||||
high_points[i]=v[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
for i=1,6 do
|
||||
high_points[i]=high_points[i]*self.properties.visuals.scale
|
||||
end
|
||||
self.model_bounding_box = high_points
|
||||
self.properties.item = {
|
||||
collisionbox = {.2, high_points[2], .2, -.2, high_points[5], -.2},
|
||||
selectionbox = {high_points[1]*3, high_points[2], high_points[3], high_points[4]*3, high_points[5], high_points[6]}
|
||||
}
|
||||
end
|
||||
local function reregister_item(self, props)
|
||||
assert(self.itemstring, "no itemstring provided. Cannot create a gun without an associated itemstring.")
|
||||
@ -224,7 +254,10 @@ local function reregister_item(self, props)
|
||||
end
|
||||
end,
|
||||
on_drop = function(itemstack, user, pos)
|
||||
local cancel_drop = Guns4d.players[user:get_player_name()].control_handler:on_drop(itemstack)
|
||||
local cancel_drop
|
||||
if Guns4d.players[user:get_player_name()].control_handler then
|
||||
cancel_drop = Guns4d.players[user:get_player_name()].control_handler:on_drop(itemstack)
|
||||
end
|
||||
if (not cancel_drop) and old_on_drop then
|
||||
return old_on_drop(itemstack, user, pos)
|
||||
end
|
||||
@ -233,27 +266,12 @@ local function reregister_item(self, props)
|
||||
Guns4d.register_item(self.itemstring, {
|
||||
collisionbox = self.properties.item.collisionbox,
|
||||
selectionbox = self.properties.item.selectionbox,
|
||||
visual_size = 10*self.properties.visuals.scale,
|
||||
mesh = self.properties.visuals.mesh,
|
||||
textures = self.properties.visuals.textures,
|
||||
animation = self.properties.visuals.animations.loaded
|
||||
})
|
||||
end
|
||||
local function register_visual_entity(def, props)
|
||||
minetest.register_entity(def.name.."_visual", {
|
||||
initial_properties = {
|
||||
visual = "mesh",
|
||||
mesh = props.visuals.mesh,
|
||||
textures = props.visuals.textures,
|
||||
glow = 0,
|
||||
pointable = false,
|
||||
static_save = false,
|
||||
backface_culling = props.visuals.backface_culling
|
||||
},
|
||||
on_step = function(self)
|
||||
if not self.object:get_attach() then self.object:remove() end
|
||||
end
|
||||
})
|
||||
end
|
||||
--========================== MAIN CLASS CONSTRUCTOR ===============================
|
||||
|
||||
function gun_default:construct_base_class()
|
||||
@ -284,5 +302,4 @@ function gun_default:construct_base_class()
|
||||
self.properties = mtul.class.proxy_table:new(self.properties)
|
||||
|
||||
Guns4d.gun._registered[self.name] = self --add gun self to the registered table
|
||||
register_visual_entity(self, props) --register the visual entity
|
||||
end
|
667
classes/Gun-methods.lua
Normal file
667
classes/Gun-methods.lua
Normal file
@ -0,0 +1,667 @@
|
||||
local gun_default = Guns4d.gun
|
||||
--I dont remember why I made this, used it though lmao
|
||||
function gun_default.multiplier_coefficient(multiplier, ratio)
|
||||
return 1+((multiplier*ratio)-ratio)
|
||||
end
|
||||
function gun_default:draw()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local props = self.properties
|
||||
if props.visuals.animations[props.charging.draw_animation] then
|
||||
self:set_animation(props.visuals.animations[props.charging.draw_animation], props.charging.draw_time)
|
||||
end
|
||||
if props.sounds[props.charging.draw_sound] then
|
||||
local sounds = Guns4d.table.deep_copy(props.sounds[props.charging.draw_sound])
|
||||
self:play_sounds(sounds)
|
||||
end
|
||||
self.ammo_handler:chamber_round()
|
||||
self.rechamber_time = props.charging.draw_time
|
||||
end
|
||||
--update gun, the main function.
|
||||
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
|
||||
|
||||
--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
|
||||
|
||||
--timers
|
||||
if self.rechamber_time > 0 then
|
||||
self.rechamber_time = self.rechamber_time - dt
|
||||
else
|
||||
self.rechamber_time = 0
|
||||
end
|
||||
self.time_since_creation = self.time_since_creation + dt
|
||||
self.time_since_last_fire = self.time_since_last_fire + dt
|
||||
|
||||
if self.burst_queue > 0 then self:update_burstfire() end
|
||||
--update some vectors
|
||||
self:update_look_offsets(dt)
|
||||
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.local_dir = self:get_dir(true)
|
||||
self.paxial_dir = self:get_player_axial_dir()
|
||||
self.local_paxial_dir = self:get_player_axial_dir(true)
|
||||
self.pos = self:get_pos()+self.handler:get_pos()
|
||||
self:update_entity()
|
||||
|
||||
if self.properties.sprite_scope then
|
||||
self.sprite_scope:update()
|
||||
end
|
||||
if self.properties.crosshair then
|
||||
self.crosshair:update()
|
||||
end
|
||||
local total_offset = self.total_offsets
|
||||
--axis rotations
|
||||
total_offset.player_axial.x = 0; total_offset.player_axial.y = 0
|
||||
total_offset.gun_axial.x = 0; total_offset.gun_axial.y = 0
|
||||
--translations
|
||||
total_offset.player_trans.x = 0; total_offset.player_trans.y = 0; total_offset.player_trans.z = 0
|
||||
total_offset.gun_trans.x = 0; total_offset.gun_trans.y = 0; total_offset.gun_trans.z = 0
|
||||
total_offset.look_trans.x = 0; total_offset.look_trans.y = 0; total_offset.look_trans.z = 0
|
||||
--this doesnt work.
|
||||
for type, _ in pairs(total_offset) do
|
||||
for i, offset in pairs(self.offsets) do
|
||||
if offset[type] and (self.consts.HAS_GUN_AXIAL_OFFSETS or type~="gun_axial") then
|
||||
total_offset[type] = total_offset[type]+offset[type]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--update modifiers
|
||||
|
||||
--manage burstfire
|
||||
function gun_default:update_burstfire()
|
||||
if self.rechamber_time <= 0 then
|
||||
local success = self:attempt_fire()
|
||||
if not success then
|
||||
self.burst_queue = 0
|
||||
else
|
||||
self.burst_queue = self.burst_queue - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
--cycle firemodes, typically activated by default_controls.lua.
|
||||
function gun_default:cycle_firemodes()
|
||||
--cannot get length using length operator because it's a proxy table
|
||||
local length = 0
|
||||
for i, v in ipairs(self.properties.firemodes) do
|
||||
length = length+1
|
||||
end
|
||||
self.current_firemode = ((self.current_firemode)%(length))+1
|
||||
|
||||
self.meta:set_int("guns4d_firemode", self.current_firemode)
|
||||
self:update_image_and_text_meta()
|
||||
self.player:set_wielded_item(self.itemstack)
|
||||
end
|
||||
--remember to set_wielded_item to self.itemstack! otherwise these changes will not apply!
|
||||
function gun_default:update_image_and_text_meta(meta)
|
||||
meta = meta or self.meta
|
||||
local ammo = self.ammo_handler.ammo
|
||||
--set the counter
|
||||
if ammo.total_bullets == 0 then
|
||||
meta:set_string("count_meta", Guns4d.config.empty_symbol)
|
||||
else
|
||||
if Guns4d.config.show_gun_inv_ammo_count then
|
||||
meta:set_string("count_meta", tostring(ammo.total_bullets))
|
||||
else
|
||||
meta:set_string("count_meta", "F")
|
||||
end
|
||||
end
|
||||
--pick the image
|
||||
local image = self.properties.inventory_image
|
||||
if (ammo.total_bullets > 0) and not ammo.magazine_psuedo_empty then
|
||||
image = self.properties.inventory_image
|
||||
elseif self.properties.inventory_image_magless and ( (ammo.loaded_mag == "empty") or (ammo.loaded_mag == "") or ammo.magazine_psuedo_empty) then
|
||||
image = self.properties.inventory_image_magless
|
||||
elseif self.properties.inventory_image_empty then
|
||||
image = self.properties.inventory_image_empty
|
||||
end
|
||||
--add the firemode overlay to the image
|
||||
local firemodes = 0
|
||||
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]]
|
||||
end
|
||||
if self.handler.infinite_ammo then
|
||||
image = image.."^"..self.properties.infinite_inventory_overlay
|
||||
end
|
||||
meta:set_string("inventory_image", image)
|
||||
end
|
||||
function gun_default:attempt_fire()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if self.rechamber_time <= 0 and not self.ammo_handler.ammo.magazine_psuedo_empty then
|
||||
local spent_bullet = self.ammo_handler:spend_round()
|
||||
if spent_bullet and spent_bullet ~= "empty" then
|
||||
local dir = self.dir
|
||||
local pos = self.pos
|
||||
local props = self.properties
|
||||
|
||||
if not Guns4d.ammo.registered_bullets[spent_bullet] then
|
||||
minetest.log("error", "unregistered bullet itemstring"..tostring(spent_bullet)..", could not fire gun (player:"..self.player:get_player_name()..")");
|
||||
return false
|
||||
end
|
||||
|
||||
--begin subtasks
|
||||
local bullet_def = Guns4d.table.fill(Guns4d.ammo.registered_bullets[spent_bullet], {
|
||||
player = self.player,
|
||||
--we don't want it to be doing fuckshit and letting players shoot through walls.
|
||||
pos = pos-((self.handler.control_handler.ads and dir*props.ads.offset.z) or dir*props.hip.offset.z),
|
||||
--dir = dir, this is now collected directly by calling get_dir so pellets and spread can be handled by the bullet_ray instance.
|
||||
gun = self
|
||||
})
|
||||
Guns4d.bullet_ray:new(bullet_def)
|
||||
if props.visuals.animations.fire then
|
||||
self:set_animation(props.visuals.animations.fire, nil, false)
|
||||
end
|
||||
self:recoil()
|
||||
self:muzzle_flash()
|
||||
--[[if props.durability.shot_per_wear then
|
||||
self:damage()
|
||||
end]]
|
||||
--print(dump(self.properties.sounds.fire))
|
||||
local fire_sound = Guns4d.table.deep_copy(props.sounds.fire) --important that we copy because play_sounds modifies it.
|
||||
fire_sound.pos = self.pos
|
||||
self:play_sounds(fire_sound)
|
||||
|
||||
self.rechamber_time = 60/props.firerateRPM
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
--[[function gun_default:damage()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
self.itemstack:set_wear(self.itemstack:get_wear()-self.properties.durability.shot_per_wear)
|
||||
self.player:set_wielded_item(self.itemstack)
|
||||
end]]
|
||||
local function rand_sign(b)
|
||||
b = b or .5
|
||||
local int = 1
|
||||
if math.random() > b then int=-1 end
|
||||
return int
|
||||
end
|
||||
function gun_default:recoil()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local rprops = self.properties.recoil
|
||||
for axis, recoil in pairs(self.velocities.recoil) do
|
||||
for _, i in pairs({"x","y"}) do
|
||||
recoil[i] = recoil[i] + (rprops.angular_velocity[axis][i]
|
||||
*rand_sign((rprops.bias[axis][i]/2)+.5))
|
||||
*self.multiplier_coefficient(rprops.hipfire_multiplier[axis], 1-self.control_handler.ads_location)
|
||||
--set original velocity
|
||||
self.velocities.init_recoil[axis][i] = recoil[i]
|
||||
end
|
||||
local length = math.sqrt(recoil.x^2+recoil.y^2)
|
||||
if length > rprops.angular_velocity_max[axis] then
|
||||
local co = rprops.angular_velocity_max[axis]*length
|
||||
recoil.x = recoil.x*co
|
||||
recoil.y = recoil.y*co
|
||||
end
|
||||
end
|
||||
self.time_since_last_fire = 0
|
||||
end
|
||||
function gun_default:update_look_offsets(dt)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local handler = self.handler
|
||||
local look_rotation = handler.look_rotation --remember that this is in counterclock-wise rotation. For 4dguns we use clockwise so it makes a bit more sense for recoil. So it needs to be inverted.
|
||||
local player_rot = self.player_rotation
|
||||
player_rot.y = -handler.look_rotation.y
|
||||
local rot_factor = Guns4d.config.vertical_rotation_factor*dt
|
||||
rot_factor = rot_factor
|
||||
local next_vert_aim = ((player_rot.x-look_rotation.x)/(1+rot_factor))+look_rotation.x --difference divided by a value and then added back to the original
|
||||
if math.abs(look_rotation.x-next_vert_aim) > .005 then
|
||||
player_rot.x = next_vert_aim
|
||||
else
|
||||
player_rot.x = look_rotation.x
|
||||
end
|
||||
|
||||
local props = self.properties
|
||||
local hip = props.hip
|
||||
local ads = props.ads
|
||||
if not handler.control_handler.ads then
|
||||
--hipfire rotation offsets
|
||||
local pitch = self.total_offsets.player_axial.x+player_rot.x
|
||||
local gun_axial = self.offsets.look.gun_axial
|
||||
local offset = handler.look_rotation.x-player_rot.x
|
||||
gun_axial.x = Guns4d.math.clamp(offset, 0, 15*(offset/math.abs(offset)))
|
||||
gun_axial.x = gun_axial.x+(pitch*(1-hip.axis_rotation_ratio))
|
||||
self.offsets.look.player_axial.x = -pitch*(1-hip.axis_rotation_ratio)
|
||||
self.offsets.look.look_trans.x = 0
|
||||
else
|
||||
self.offsets.look.gun_axial.x = 0
|
||||
self.offsets.look.player_axial.x = 0
|
||||
end
|
||||
local location = Guns4d.math.clamp(Guns4d.math.smooth_ratio(self.control_handler.ads_location)*2, 0, 1)
|
||||
self.offsets.look.look_trans.x = ads.horizontal_offset*location
|
||||
local fwd_offset = 0
|
||||
if look_rotation.x < 0 then --minetest's pitch is inverted, checking here if it's above horizon.
|
||||
fwd_offset = math.abs(math.sin(look_rotation.x*math.pi/180))*props.ads.offset.z*location
|
||||
end
|
||||
self.offsets.look.player_trans.z = fwd_offset
|
||||
self.offsets.look.look_trans.z = fwd_offset
|
||||
end
|
||||
--============================================== positional info =====================================
|
||||
--all of this dir shit needs to be optimized HARD
|
||||
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)
|
||||
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
|
||||
--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
|
||||
dir = vector.rotate(dir, {x=self.player_rotation.x*math.pi/180,y=self.player_rotation.y*math.pi/180,z=0})
|
||||
end
|
||||
end
|
||||
return dir
|
||||
end
|
||||
--this needs to be optimized because it may be called frequently...
|
||||
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
|
||||
--Should probably optimize this at some point.
|
||||
local zero = vector.zero()
|
||||
function gun_default:get_pos(offset_pos, relative, ads, ignore_translations)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local player = self.player
|
||||
local handler = self.handler
|
||||
local bone_location = handler.player_model_handler.gun_bone_location
|
||||
local gun_translation = self.gun_translation
|
||||
if offset_pos then
|
||||
gun_translation = gun_translation+offset_pos
|
||||
end
|
||||
if gun_translation==self.gun_translation then gun_translation = vector.new(gun_translation) end
|
||||
--dir needs to be rotated twice seperately to avoid weirdness
|
||||
local pos
|
||||
if not relative then
|
||||
pos = vector.rotate(bone_location, {x=0, y=-handler.look_rotation.y*math.pi/180, z=0})
|
||||
pos = pos+vector.rotate(gun_translation, vector.dir_to_rotation(self.paxial_dir))
|
||||
else
|
||||
pos = vector.rotate(gun_translation, vector.dir_to_rotation(self.local_paxial_dir)+{x=self.player_rotation.x*math.pi/180,y=0,z=0})+bone_location
|
||||
end
|
||||
--[[local hud_pos
|
||||
if relative then
|
||||
hud_pos = vector.rotate(pos, {x=0,y=player:get_look_horizontal(),z=0})+handler:get_pos()
|
||||
else
|
||||
hud_pos = pos+handler:get_pos()
|
||||
end]]
|
||||
if minetest.get_player_by_name("fatal2") then
|
||||
--[[local hud = minetest.get_player_by_name("fatal2"):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)
|
||||
minetest.get_player_by_name("fatal2"):hud_remove(hud)
|
||||
end, hud)]]
|
||||
end
|
||||
|
||||
--world pos, position of bone, offset of gun from bone (with added_pos)
|
||||
return pos
|
||||
end
|
||||
|
||||
|
||||
--=============================================== 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(), "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()
|
||||
end
|
||||
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
|
||||
visibility = false
|
||||
end
|
||||
--Irrlicht uses counterclockwise but we use clockwise.
|
||||
local pos = self.gun_translation
|
||||
local ads = props.ads.offset
|
||||
local hip = props.hip.offset
|
||||
local offset = self.total_offsets.gun_trans
|
||||
local ip = Guns4d.math.smooth_ratio(Guns4d.math.clamp(handler.control_handler.ads_location*2,0,1))
|
||||
local ip_inv = 1-ip
|
||||
pos.x = (ads.x*ip)+(hip.x*ip_inv)+offset.x
|
||||
pos.y = (ads.y*ip)+(hip.y*ip_inv)+offset.y
|
||||
pos.z = (ads.z*ip)+(hip.z*ip_inv)+offset.z
|
||||
self.gun_translation = pos
|
||||
obj:set_attach(player, handler.player_model_handler.bone_aliases.gun, {x=pos.x*10, y=pos.y*10, z=pos.z*10}, -axial_rot, visibility)
|
||||
end
|
||||
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
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function gun_default:update_wag(dt)
|
||||
local handler = self.handler
|
||||
local wag = self.offsets.walking
|
||||
local velocity = wag.velocity
|
||||
local props = self.properties
|
||||
local old_tick
|
||||
if handler.walking then
|
||||
velocity = self.player:get_velocity()
|
||||
wag.velocity = velocity
|
||||
end
|
||||
old_tick = old_tick or wag.tick
|
||||
if velocity then
|
||||
if handler.walking then
|
||||
wag.tick = wag.tick + (dt*vector.length(velocity))
|
||||
else
|
||||
wag.tick = wag.tick + (dt*4)
|
||||
end
|
||||
end
|
||||
local walking_offset = self.offsets.walking
|
||||
if velocity and (not handler.walking) and (math.ceil(old_tick/props.wag.cycle_speed)+.5 < (math.ceil(wag.tick/props.wag.cycle_speed))+.5) and (wag.tick > old_tick) then
|
||||
wag.velocity = nil
|
||||
return
|
||||
end
|
||||
for _, i in ipairs({"x","y"}) do
|
||||
for _, axis in ipairs({"player_axial", "gun_axial"}) do
|
||||
if velocity then
|
||||
local multiplier = 1
|
||||
if i == "x" then
|
||||
multiplier = 2
|
||||
end
|
||||
--if the result is negative we know that it's flipped, and thus can be ended.
|
||||
local inp = (wag.tick/props.wag.cycle_speed)*math.pi*multiplier
|
||||
--this is a mess, I think that 1.6 is the frequency of human steps or something
|
||||
walking_offset[axis][i] = math.sin(inp)*self.properties.wag.offset[axis][i]
|
||||
else
|
||||
local old_value = walking_offset[axis][i]
|
||||
if math.abs(walking_offset[axis][i]) > .005 then
|
||||
local multiplier = 1/props.wag.decay_speed
|
||||
walking_offset[axis][i] = walking_offset[axis][i]-(walking_offset[axis][i]*multiplier*dt)
|
||||
else
|
||||
walking_offset[axis][i] = 0
|
||||
end
|
||||
if math.abs(walking_offset[axis][i]) > math.abs(old_value) then
|
||||
walking_offset[axis][i] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local e = 2.7182818284590452353602874713527 --I don't know how to find it otherwise...
|
||||
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]
|
||||
local recoil_vel = Guns4d.math.clamp(self.velocities.recoil[axis][i],-self.properties.recoil.angular_velocity_max[axis],self.properties.recoil.angular_velocity_max[axis])
|
||||
local old_recoil_vel = recoil_vel
|
||||
recoil = recoil + recoil_vel
|
||||
--this is modelled off a geometric sequence where the Y incercept of the sequence is set to recoil_vel.
|
||||
if math.abs(recoil_vel) > 0.001 then
|
||||
local r = (10*self.properties.recoil.velocity_correction_factor[axis])^-1
|
||||
local vel_co = e^-( (self.time_since_last_fire^2)/(2*r^2) )
|
||||
recoil_vel = self.velocities.init_recoil[axis][i]*vel_co
|
||||
else
|
||||
recoil_vel = 0
|
||||
end
|
||||
self.velocities.recoil[axis][i] = recoil_vel
|
||||
|
||||
--ax^2+bx+c
|
||||
--recoil_velocity_correction_rate
|
||||
--recoil_correction_rate
|
||||
local old_recoil = recoil
|
||||
local abs = math.abs(recoil)
|
||||
local sign = old_recoil/abs
|
||||
if abs > 0.001 then
|
||||
local correction_value = abs*self.time_since_last_fire*self.properties.recoil.target_correction_factor[axis]
|
||||
correction_value = Guns4d.math.clamp(correction_value, 0, self.properties.recoil.target_correction_max_rate[axis])
|
||||
abs=abs-(correction_value*dt)
|
||||
--prevent overcorrection
|
||||
if abs < 0 then
|
||||
abs = 0
|
||||
end
|
||||
end
|
||||
if sign~=sign then
|
||||
sign = 1
|
||||
end
|
||||
self.offsets.recoil[axis][i] = abs*sign
|
||||
end
|
||||
end
|
||||
end
|
||||
function gun_default:update_animation(dt)
|
||||
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
|
||||
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.
|
||||
function gun_default:set_animation(frames, length, fps, loop)
|
||||
loop = loop or false --why the fuck default is true? I DONT FUCKIN KNOW (this undoes this)
|
||||
assert(type(frames)=="table" and frames.x and frames.y, "frames invalid or nil in set_animation()!")
|
||||
assert(not (length and fps), "cannot play animation with both specified length and specified fps. Only one parameter can be used.")
|
||||
local num_frames = math.abs(frames.x-frames.y)
|
||||
if length then
|
||||
fps = num_frames/length
|
||||
elseif not fps then
|
||||
fps = self.consts.DEFAULT_FPS
|
||||
end
|
||||
self.entity:set_animation(frames, fps, 0, loop) --see init.lua for modified ObjRef stuff.
|
||||
end
|
||||
function gun_default:clear_animation()
|
||||
local loaded = false
|
||||
if self.properties.ammo.magazine_only then
|
||||
if self.ammo_handler.ammo.loaded_mag ~= "empty" then
|
||||
loaded = true
|
||||
end
|
||||
elseif self.ammo_handler.ammo.total_bullets > 0 then
|
||||
loaded = true
|
||||
end
|
||||
if loaded then
|
||||
self.entity:set_animation({x=self.properties.visuals.animations.loaded.x, y=self.properties.visuals.animations.loaded.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
|
||||
else
|
||||
self.entity:set_animation({x=self.properties.visuals.animations.empty.x, y=self.properties.visuals.animations.empty.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
|
||||
end
|
||||
end
|
||||
local function adjust_gain(tbl, v)
|
||||
v = tbl.third_person_gain_multiplier or v
|
||||
for i = 1, #tbl do
|
||||
adjust_gain(tbl[i], v)
|
||||
end
|
||||
if tbl.gain and (tbl.split_audio_by_perspective~=false) then
|
||||
if type(tbl.gain) == "number" then
|
||||
tbl.gain = tbl.gain*v
|
||||
else
|
||||
tbl.gain.min = tbl.gain.min*v
|
||||
tbl.gain.max = tbl.gain.max*v
|
||||
end
|
||||
end
|
||||
end
|
||||
function gun_default:play_sounds(sound)
|
||||
local thpson_sound = Guns4d.table.deep_copy(sound)
|
||||
local fsprsn_sound = Guns4d.table.deep_copy(sound)
|
||||
|
||||
thpson_sound.pos = self.pos
|
||||
thpson_sound.player = self.player
|
||||
thpson_sound.exclude_player = self.player
|
||||
adjust_gain(thpson_sound, self.consts.THIRD_PERSON_GAIN_MULTIPLIER)
|
||||
|
||||
fsprsn_sound.player = self.player
|
||||
fsprsn_sound.to_player = "from_player"
|
||||
|
||||
return Guns4d.play_sounds(thpson_sound), Guns4d.play_sounds(fsprsn_sound)
|
||||
end
|
||||
function gun_default:update_breathing(dt)
|
||||
assert(self.instance)
|
||||
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.
|
||||
local x = (self.time_since_creation%breathing_info.rate)*math.pi/(breathing_info.rate-breathing_info.pause)
|
||||
local scale = self.properties.breathing_scale
|
||||
--now if it's above math.pi we know it's in the pause half of the cycle. For smoothness, we cut the sine off early and decay the value non-linearly.
|
||||
--not sure why 8/9 is a constant here... I assume it's if it's 8/9 of the way through the cycle. Not going to worry about it.
|
||||
if x > math.pi*(8/9) then
|
||||
self.offsets.breathing.player_axial.x=self.offsets.breathing.player_axial.x-(self.offsets.breathing.player_axial.x*2*dt)
|
||||
else
|
||||
self.offsets.breathing.player_axial.x = scale*(math.sin(x))
|
||||
end
|
||||
end
|
||||
|
||||
function gun_default:update_sway(dt)
|
||||
assert(self.instance, "attempt to call object method from a base class")
|
||||
local sprops = self.properties.sway
|
||||
for axis, sway in pairs(self.offsets.sway) do
|
||||
local sway_vel = self.velocities.sway[axis]
|
||||
local ran
|
||||
ran = vector.apply(vector.new(), function(i,v)
|
||||
if i ~= "x" then
|
||||
return (math.random()-.5)*2
|
||||
end
|
||||
end)
|
||||
ran.z = 0
|
||||
local vel_mul = self.multiplier_coefficient(sprops.hipfire_velocity_multiplier[axis], 1-self.control_handler.ads_location)
|
||||
sway_vel = vector.normalize(sway_vel+(ran*dt))*sprops.angular_velocity[axis]*vel_mul
|
||||
sway=sway+(sway_vel*dt)
|
||||
local len_mul = self.multiplier_coefficient(sprops.hipfire_angle_multiplier[axis], 1-self.control_handler.ads_location)
|
||||
if vector.length(sway) > sprops.max_angle[axis]*len_mul then
|
||||
sway=vector.normalize(sway)*sprops.max_angle[axis]*len_mul
|
||||
sway_vel = vector.new()
|
||||
end
|
||||
self.offsets.sway[axis] = sway
|
||||
self.velocities.sway[axis] = sway_vel
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--should merge these functions eventually...
|
||||
function gun_default:update_animation_rotation()
|
||||
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 out
|
||||
if self.b3d_model.global_frames.rotation then
|
||||
if self.b3d_model.global_frames.rotation[frame1] then
|
||||
if (not self.b3d_model.global_frames.rotation[frame2]) or (current_frame==frame1) then
|
||||
out = vector.new(self.b3d_model.global_frames.rotation[frame1]:to_euler_angles_unpack())*180/math.pi
|
||||
--print("rawsent")
|
||||
else --to stop nan
|
||||
local ip_ratio = (current_frame-frame1)/(frame2-frame1)
|
||||
local vec1 = self.b3d_model.global_frames.rotation[frame1]
|
||||
local vec2 = self.b3d_model.global_frames.rotation[frame2]
|
||||
out = vector.new(vec1:slerp(vec2, ip_ratio):to_euler_angles_unpack())*180/math.pi
|
||||
end
|
||||
else
|
||||
out = vector.copy(self.b3d_model.global_frames.rotation[1])
|
||||
end
|
||||
--print(frame1, frame2, current_frame, dump(out))
|
||||
else
|
||||
out = vector.new()
|
||||
end
|
||||
self.animation_rotation = out
|
||||
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+1
|
||||
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
|
||||
|
||||
for i, v in pairs(out) do
|
||||
if self.b3d_model.global_frames[i] then
|
||||
if self.b3d_model.global_frames[i][frame1] then
|
||||
if (not self.b3d_model.global_frames[i][frame2]) or (current_frame==frame1) then
|
||||
out[i] = vector.copy(self.b3d_model.global_frames[i][frame1])
|
||||
else --to stop nan
|
||||
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
|
||||
out[i]=vector.copy(self.b3d_model.global_frames[i][1])
|
||||
end
|
||||
else
|
||||
out[i] = vector.new()
|
||||
end
|
||||
end
|
||||
return out.arm_left, out.arm_right
|
||||
--return vector.copy(self.b3d_model.global_frames.arm_left[1]), vector.copy(self.b3d_model.global_frames.arm_right[1])
|
||||
end
|
||||
|
||||
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
|
||||
end
|
729
classes/Gun.lua
729
classes/Gun.lua
@ -28,10 +28,18 @@ local Vec = vector
|
||||
local gun_default = {
|
||||
--- `string` the name of the gun. Set to __template for guns which have no instances.
|
||||
name = "__guns4d:default__",
|
||||
--- `ItemStack` itemstack held by the player
|
||||
--- `ItemStack` the gun itemstack. Remember to player:set_wielded_item(self.itemstack) when making meta or itemstack changes.
|
||||
itemstack = nil,
|
||||
--- `MetaDataRef` itemstack meta
|
||||
meta = nil,
|
||||
--- `string` the ID of the gun used for tracking of it's inventory
|
||||
id = nil,
|
||||
--- `ObjRef` the gun entity
|
||||
gun_entity = nil,
|
||||
--- `string` inventory image for when the gun has no magazine
|
||||
inventory_image_magless = nil,
|
||||
--- `string` inventory image for when the gun is loaded. This is added automatically during construction.
|
||||
inventory_image = nil,
|
||||
--- `string` the itemstring of the gun- i.e. "guns4d_pack_1:m4". Set to "" for __template guns.
|
||||
itemstring = "",
|
||||
--- list of registered guns, **DO NOT MODIFY** I really need a metatable for this class...
|
||||
@ -56,6 +64,8 @@ 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.
|
||||
property_modifiers = nil,
|
||||
|
||||
--- properties
|
||||
--
|
||||
@ -80,11 +90,11 @@ local gun_default = {
|
||||
flash_offset = Vec.new(),
|
||||
--- `int`=600 The number of rounds (cartidges) this gun can throw per minute. Used by update(), fire() and default controls
|
||||
firerateRPM = 600,
|
||||
--- the item entity's attributes. This will later include held item definition...
|
||||
item = {
|
||||
--- the item entity's attributes. [DOCUMENTATION NEEDED]
|
||||
--[[item = {
|
||||
collisionbox = ((not Guns4d.config.realistic_items) and {-.1,-.1,-.1, .1,.1,.1}) or {-.1,-.05,-.1, .1,.15,.1},
|
||||
selectionbox = {-.1,-.1,-.1, .1,.1,.1}
|
||||
},
|
||||
},]]
|
||||
--- properties.hip
|
||||
-- @table gun.properties.hip
|
||||
-- @compact
|
||||
@ -252,6 +262,31 @@ local gun_default = {
|
||||
on_secondary_use = Guns4d.default_touch_controls.on_secondary_use,
|
||||
firemode = Guns4d.default_touch_controls.firemode
|
||||
},
|
||||
--[[ parts framework coming soon. example for m4
|
||||
parts = {
|
||||
barrel = {
|
||||
operable_without = false,
|
||||
group = "guns4d_m4_barrel"
|
||||
default = "guns4d:m4_15in"
|
||||
}
|
||||
}
|
||||
]]
|
||||
inventory = {
|
||||
--[[attachment_slots = {
|
||||
underbarrel = {
|
||||
formspec_inventory_location = {x=0, y=1}
|
||||
slots = 2,
|
||||
rail = "picatinny" --only attachments fit for this type will be usable.
|
||||
allowed = {
|
||||
"group:guns4d_underbarrel"
|
||||
},
|
||||
bone = "" --the bone both to attach to and to display at on the menu.
|
||||
}
|
||||
},]]
|
||||
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.
|
||||
render_image = "m4_ortho.png", --expects an image of the right side of the gun, where the gun is facing the right.
|
||||
--rendered_from_model = true --if true the rendering is automatically moved to the center of the screen
|
||||
},
|
||||
--- properties.charging
|
||||
--
|
||||
-- @table gun.properties.charging
|
||||
@ -300,6 +335,12 @@ local gun_default = {
|
||||
-- @table gun.properties.visuals
|
||||
-- @compact
|
||||
visuals = {
|
||||
--- name of mesh to display
|
||||
mesh = nil,
|
||||
--- list of textures to use
|
||||
textures = {},
|
||||
--- scale multiplier
|
||||
scale = 1,
|
||||
--- toggles backface culling
|
||||
backface_culling = true,
|
||||
--- a table of animations in the format {x=int, y=float}. Indexes define the name of the animation to be refrenced by other functions of the gun.
|
||||
@ -308,6 +349,8 @@ local gun_default = {
|
||||
loaded = {x=1,y=1},
|
||||
},
|
||||
},
|
||||
--- a table {x1,y1,z1,x2,y2,z2} specifying the bounding box of the model. The first 3 (x1,y1,z1) are the lower of their counterparts
|
||||
model_bounding_box = nil,
|
||||
--- a table of @{guns4d_soundspec|soundspecs} to be referenced by other functions
|
||||
sounds = { --this does not contain reload sound effects.
|
||||
fire = {
|
||||
@ -340,7 +383,8 @@ local gun_default = {
|
||||
}
|
||||
},
|
||||
},
|
||||
ammo_handler = Ammo_handler,
|
||||
ammo_handler = Guns4d.ammo_handler,
|
||||
attachment_handler = Guns4d.attachment_handler,
|
||||
sprite_scope = nil,
|
||||
crosshair = nil,
|
||||
initial_vertical_rotation = -60,
|
||||
@ -423,8 +467,6 @@ local gun_default = {
|
||||
-- @table lvl1_fields.consts
|
||||
-- @compact
|
||||
consts = {
|
||||
---
|
||||
AIM_OUT_AIM_IN_SPEED_RATIO = 2.5,
|
||||
--- frequency of keyframe samples for animation offsets and
|
||||
KEYFRAME_SAMPLE_PRECISION = .1,
|
||||
--- default max hear distance when not specified
|
||||
@ -458,666 +500,25 @@ local gun_default = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
--I dont remember why I made this, used it though lmao
|
||||
function gun_default.multiplier_coefficient(multiplier, ratio)
|
||||
return 1+((multiplier*ratio)-ratio)
|
||||
end
|
||||
function gun_default:draw()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local props = self.properties
|
||||
if props.visuals.animations[props.charging.draw_animation] then
|
||||
self:set_animation(props.visuals.animations[props.charging.draw_animation], props.charging.draw_time)
|
||||
minetest.register_entity("guns4d:gun_entity", {
|
||||
initial_properties = {
|
||||
visual = "mesh",
|
||||
mesh = "",
|
||||
textures = {},
|
||||
glow = 0,
|
||||
pointable = false,
|
||||
static_save = false,
|
||||
visual_size = {x=10,y=10,z=10},
|
||||
backface_culling = false
|
||||
},
|
||||
on_step = function(self)
|
||||
if not self.object:get_attach() then self.object:remove() end
|
||||
end
|
||||
if props.sounds[props.charging.draw_sound] then
|
||||
local sounds = Guns4d.table.deep_copy(props.sounds[props.charging.draw_sound])
|
||||
self:play_sounds(sounds)
|
||||
end
|
||||
self.ammo_handler:chamber_round()
|
||||
self.rechamber_time = props.charging.draw_time
|
||||
end
|
||||
--update gun, the main function.
|
||||
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
|
||||
|
||||
--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
|
||||
|
||||
--timers
|
||||
if self.rechamber_time > 0 then
|
||||
self.rechamber_time = self.rechamber_time - dt
|
||||
else
|
||||
self.rechamber_time = 0
|
||||
end
|
||||
self.time_since_creation = self.time_since_creation + dt
|
||||
self.time_since_last_fire = self.time_since_last_fire + dt
|
||||
|
||||
if self.burst_queue > 0 then self:update_burstfire() end
|
||||
--update some vectors
|
||||
self:update_look_offsets(dt)
|
||||
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.local_dir = self:get_dir(true)
|
||||
self.paxial_dir = self:get_player_axial_dir()
|
||||
self.local_paxial_dir = self:get_player_axial_dir(true)
|
||||
self.pos = self:get_pos()+self.handler:get_pos()
|
||||
self:update_entity()
|
||||
|
||||
if self.properties.sprite_scope then
|
||||
self.sprite_scope:update()
|
||||
end
|
||||
if self.properties.crosshair then
|
||||
self.crosshair:update()
|
||||
end
|
||||
local total_offset = self.total_offsets
|
||||
--axis rotations
|
||||
total_offset.player_axial.x = 0; total_offset.player_axial.y = 0
|
||||
total_offset.gun_axial.x = 0; total_offset.gun_axial.y = 0
|
||||
--translations
|
||||
total_offset.player_trans.x = 0; total_offset.player_trans.y = 0; total_offset.player_trans.z = 0
|
||||
total_offset.gun_trans.x = 0; total_offset.gun_trans.y = 0; total_offset.gun_trans.z = 0
|
||||
total_offset.look_trans.x = 0; total_offset.look_trans.y = 0; total_offset.look_trans.z = 0
|
||||
--this doesnt work.
|
||||
for type, _ in pairs(total_offset) do
|
||||
for i, offset in pairs(self.offsets) do
|
||||
if offset[type] and (self.consts.HAS_GUN_AXIAL_OFFSETS or type~="gun_axial") then
|
||||
total_offset[type] = total_offset[type]+offset[type]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function gun_default:update_burstfire()
|
||||
if self.rechamber_time <= 0 then
|
||||
local success = self:attempt_fire()
|
||||
if not success then
|
||||
self.burst_queue = 0
|
||||
else
|
||||
self.burst_queue = self.burst_queue - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
function gun_default:cycle_firemodes()
|
||||
--cannot get length using length operator because it's a proxy table
|
||||
local length = 0
|
||||
for i, v in ipairs(self.properties.firemodes) do
|
||||
length = length+1
|
||||
end
|
||||
self.current_firemode = ((self.current_firemode)%(length))+1
|
||||
|
||||
self.meta:set_int("guns4d_firemode", self.current_firemode)
|
||||
self:update_image_and_text_meta()
|
||||
self.player:set_wielded_item(self.itemstack)
|
||||
end
|
||||
--remember to set_wielded_item to self.itemstack! otherwise these changes will not apply!
|
||||
function gun_default:update_image_and_text_meta(meta)
|
||||
meta = meta or self.meta
|
||||
local ammo = self.ammo_handler.ammo
|
||||
--set the counter
|
||||
if ammo.total_bullets == 0 then
|
||||
meta:set_string("count_meta", Guns4d.config.empty_symbol)
|
||||
else
|
||||
if Guns4d.config.show_gun_inv_ammo_count then
|
||||
meta:set_string("count_meta", tostring(ammo.total_bullets))
|
||||
else
|
||||
meta:set_string("count_meta", "F")
|
||||
end
|
||||
end
|
||||
--pick the image
|
||||
local image = self.properties.inventory_image
|
||||
if (ammo.total_bullets > 0) and not ammo.magazine_psuedo_empty then
|
||||
image = self.properties.inventory_image
|
||||
elseif self.properties.inventory_image_magless and ( (ammo.loaded_mag == "empty") or (ammo.loaded_mag == "") or ammo.magazine_psuedo_empty) then
|
||||
image = self.properties.inventory_image_magless
|
||||
elseif self.properties.inventory_image_empty then
|
||||
image = self.properties.inventory_image_empty
|
||||
end
|
||||
--add the firemode overlay to the image
|
||||
local firemodes = 0
|
||||
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]]
|
||||
end
|
||||
if self.handler.infinite_ammo then
|
||||
image = image.."^"..self.properties.infinite_inventory_overlay
|
||||
end
|
||||
meta:set_string("inventory_image", image)
|
||||
end
|
||||
function gun_default:attempt_fire()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if self.rechamber_time <= 0 and not self.ammo_handler.ammo.magazine_psuedo_empty then
|
||||
local spent_bullet = self.ammo_handler:spend_round()
|
||||
if spent_bullet and spent_bullet ~= "empty" then
|
||||
local dir = self.dir
|
||||
local pos = self.pos
|
||||
local props = self.properties
|
||||
|
||||
if not Guns4d.ammo.registered_bullets[spent_bullet] then
|
||||
minetest.log("error", "unregistered bullet itemstring"..tostring(spent_bullet)..", could not fire gun (player:"..self.player:get_player_name()..")");
|
||||
return false
|
||||
end
|
||||
|
||||
--begin subtasks
|
||||
local bullet_def = Guns4d.table.fill(Guns4d.ammo.registered_bullets[spent_bullet], {
|
||||
player = self.player,
|
||||
--we don't want it to be doing fuckshit and letting players shoot through walls.
|
||||
pos = pos-((self.handler.control_handler.ads and dir*props.ads.offset.z) or dir*props.hip.offset.z),
|
||||
--dir = dir, this is now collected directly by calling get_dir so pellets and spread can be handled by the bullet_ray instance.
|
||||
gun = self
|
||||
})
|
||||
Guns4d.bullet_ray:new(bullet_def)
|
||||
if props.visuals.animations.fire then
|
||||
self:set_animation(props.visuals.animations.fire, nil, false)
|
||||
end
|
||||
self:recoil()
|
||||
self:muzzle_flash()
|
||||
--[[if props.durability.shot_per_wear then
|
||||
self:damage()
|
||||
end]]
|
||||
--print(dump(self.properties.sounds.fire))
|
||||
local fire_sound = Guns4d.table.deep_copy(props.sounds.fire) --important that we copy because play_sounds modifies it.
|
||||
fire_sound.pos = self.pos
|
||||
self:play_sounds(fire_sound)
|
||||
|
||||
self.rechamber_time = 60/props.firerateRPM
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
--[[function gun_default:damage()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
self.itemstack:set_wear(self.itemstack:get_wear()-self.properties.durability.shot_per_wear)
|
||||
self.player:set_wielded_item(self.itemstack)
|
||||
end]]
|
||||
local function rand_sign(b)
|
||||
b = b or .5
|
||||
local int = 1
|
||||
if math.random() > b then int=-1 end
|
||||
return int
|
||||
end
|
||||
function gun_default:recoil()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local rprops = self.properties.recoil
|
||||
for axis, recoil in pairs(self.velocities.recoil) do
|
||||
for _, i in pairs({"x","y"}) do
|
||||
recoil[i] = recoil[i] + (rprops.angular_velocity[axis][i]
|
||||
*rand_sign((rprops.bias[axis][i]/2)+.5))
|
||||
*self.multiplier_coefficient(rprops.hipfire_multiplier[axis], 1-self.control_handler.ads_location)
|
||||
--set original velocity
|
||||
self.velocities.init_recoil[axis][i] = recoil[i]
|
||||
end
|
||||
local length = math.sqrt(recoil.x^2+recoil.y^2)
|
||||
if length > rprops.angular_velocity_max[axis] then
|
||||
local co = rprops.angular_velocity_max[axis]*length
|
||||
recoil.x = recoil.x*co
|
||||
recoil.y = recoil.y*co
|
||||
end
|
||||
end
|
||||
self.time_since_last_fire = 0
|
||||
end
|
||||
function gun_default:update_look_offsets(dt)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local handler = self.handler
|
||||
local look_rotation = handler.look_rotation --remember that this is in counterclock-wise rotation. For 4dguns we use clockwise so it makes a bit more sense for recoil. So it needs to be inverted.
|
||||
local player_rot = self.player_rotation
|
||||
player_rot.y = -handler.look_rotation.y
|
||||
local rot_factor = Guns4d.config.vertical_rotation_factor*dt
|
||||
rot_factor = rot_factor
|
||||
local next_vert_aim = ((player_rot.x-look_rotation.x)/(1+rot_factor))+look_rotation.x --difference divided by a value and then added back to the original
|
||||
if math.abs(look_rotation.x-next_vert_aim) > .005 then
|
||||
player_rot.x = next_vert_aim
|
||||
else
|
||||
player_rot.x = look_rotation.x
|
||||
end
|
||||
|
||||
local props = self.properties
|
||||
local hip = props.hip
|
||||
local ads = props.ads
|
||||
if not handler.control_handler.ads then
|
||||
--hipfire rotation offsets
|
||||
local pitch = self.total_offsets.player_axial.x+player_rot.x
|
||||
local gun_axial = self.offsets.look.gun_axial
|
||||
local offset = handler.look_rotation.x-player_rot.x
|
||||
gun_axial.x = Guns4d.math.clamp(offset, 0, 15*(offset/math.abs(offset)))
|
||||
gun_axial.x = gun_axial.x+(pitch*(1-hip.axis_rotation_ratio))
|
||||
self.offsets.look.player_axial.x = -pitch*(1-hip.axis_rotation_ratio)
|
||||
self.offsets.look.look_trans.x = 0
|
||||
else
|
||||
self.offsets.look.gun_axial.x = 0
|
||||
self.offsets.look.player_axial.x = 0
|
||||
end
|
||||
local location = Guns4d.math.clamp(Guns4d.math.smooth_ratio(self.control_handler.ads_location)*2, 0, 1)
|
||||
self.offsets.look.look_trans.x = ads.horizontal_offset*location
|
||||
local fwd_offset = 0
|
||||
if look_rotation.x < 0 then --minetest's pitch is inverted, checking here if it's above horizon.
|
||||
fwd_offset = math.abs(math.sin(look_rotation.x*math.pi/180))*props.ads.offset.z*location
|
||||
end
|
||||
self.offsets.look.player_trans.z = fwd_offset
|
||||
self.offsets.look.look_trans.z = fwd_offset
|
||||
end
|
||||
--============================================== positional info =====================================
|
||||
--all of this dir shit needs to be optimized HARD
|
||||
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 = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=rotation.gun_axial.x*math.pi/180, z=0}))
|
||||
dir = Vec.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)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local handler = self.handler
|
||||
local rotation = self.total_offsets
|
||||
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.player_axial.x)*math.pi/180), z=0}))
|
||||
dir = Vec.rotate(dir, {y=((rotation.player_axial.y)*math.pi/180), x=0, z=0})
|
||||
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
|
||||
--we need the head rotation in either of these cases, as that's what they're showing.
|
||||
dir = Vec.rotate(dir, {x=handler.look_rotation.x*math.pi/180,y=-handler.look_rotation.y*math.pi/180,z=0})
|
||||
else
|
||||
dir = Vec.rotate(dir, {x=self.player_rotation.x*math.pi/180,y=self.player_rotation.y*math.pi/180,z=0})
|
||||
end
|
||||
end
|
||||
return dir
|
||||
end
|
||||
--this needs to be optimized because it may be called frequently...
|
||||
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
|
||||
--Should probably optimize this at some point.
|
||||
local zero = vector.zero()
|
||||
function gun_default:get_pos(offset_pos, relative, ads, ignore_translations)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local player = self.player
|
||||
local handler = self.handler
|
||||
local bone_location = handler.player_model_handler.gun_bone_location
|
||||
local gun_translation = self.gun_translation
|
||||
if offset_pos then
|
||||
gun_translation = gun_translation+offset_pos
|
||||
end
|
||||
if gun_translation==self.gun_translation then gun_translation = vector.new(gun_translation) end
|
||||
--dir needs to be rotated twice seperately to avoid weirdness
|
||||
local pos
|
||||
if not relative then
|
||||
pos = Vec.rotate(bone_location, {x=0, y=-handler.look_rotation.y*math.pi/180, z=0})
|
||||
pos = pos+Vec.rotate(gun_translation, Vec.dir_to_rotation(self.paxial_dir))
|
||||
else
|
||||
pos = Vec.rotate(gun_translation, Vec.dir_to_rotation(self.local_paxial_dir)+{x=self.player_rotation.x*math.pi/180,y=0,z=0})+bone_location
|
||||
end
|
||||
--[[local hud_pos
|
||||
if relative then
|
||||
hud_pos = vector.rotate(pos, {x=0,y=player:get_look_horizontal(),z=0})+handler:get_pos()
|
||||
else
|
||||
hud_pos = pos+handler:get_pos()
|
||||
end]]
|
||||
if minetest.get_player_by_name("fatal2") then
|
||||
--[[local hud = minetest.get_player_by_name("fatal2"):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)
|
||||
minetest.get_player_by_name("fatal2"):hud_remove(hud)
|
||||
end, hud)]]
|
||||
end
|
||||
|
||||
--world pos, position of bone, offset of gun from bone (with added_pos)
|
||||
return pos
|
||||
end
|
||||
|
||||
|
||||
--=============================================== 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()
|
||||
--obj.parent_player = self.player
|
||||
Guns4d.gun_by_ObjRef[self.entity] = self
|
||||
--obj:on_step()
|
||||
--self:update_entity()
|
||||
end
|
||||
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
|
||||
visibility = false
|
||||
end
|
||||
--Irrlicht uses counterclockwise but we use clockwise.
|
||||
local pos = self.gun_translation
|
||||
local ads = props.ads.offset
|
||||
local hip = props.hip.offset
|
||||
local offset = self.total_offsets.gun_trans
|
||||
local ip = Guns4d.math.smooth_ratio(handler.control_handler.ads_location)
|
||||
local ip_inv = 1-ip
|
||||
pos.x = (ads.x*ip)+(hip.x*ip_inv)+offset.x
|
||||
pos.y = (ads.y*ip)+(hip.y*ip_inv)+offset.y
|
||||
pos.z = (ads.z*ip)+(hip.z*ip_inv)+offset.z
|
||||
self.gun_translation = pos
|
||||
obj:set_attach(player, handler.player_model_handler.bone_aliases.gun, {x=pos.x*10, y=pos.y*10, z=pos.z*10}, -axial_rot, visibility)
|
||||
end
|
||||
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
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function gun_default:update_wag(dt)
|
||||
local handler = self.handler
|
||||
local wag = self.offsets.walking
|
||||
local velocity = wag.velocity
|
||||
local props = self.properties
|
||||
local old_tick
|
||||
if handler.walking then
|
||||
velocity = self.player:get_velocity()
|
||||
wag.velocity = velocity
|
||||
end
|
||||
old_tick = old_tick or wag.tick
|
||||
if velocity then
|
||||
if handler.walking then
|
||||
wag.tick = wag.tick + (dt*Vec.length(velocity))
|
||||
else
|
||||
wag.tick = wag.tick + (dt*4)
|
||||
end
|
||||
end
|
||||
local walking_offset = self.offsets.walking
|
||||
if velocity and (not handler.walking) and (math.ceil(old_tick/props.wag.cycle_speed)+.5 < (math.ceil(wag.tick/props.wag.cycle_speed))+.5) and (wag.tick > old_tick) then
|
||||
wag.velocity = nil
|
||||
return
|
||||
end
|
||||
for _, i in ipairs({"x","y"}) do
|
||||
for _, axis in ipairs({"player_axial", "gun_axial"}) do
|
||||
if velocity then
|
||||
local multiplier = 1
|
||||
if i == "x" then
|
||||
multiplier = 2
|
||||
end
|
||||
--if the result is negative we know that it's flipped, and thus can be ended.
|
||||
local inp = (wag.tick/props.wag.cycle_speed)*math.pi*multiplier
|
||||
--this is a mess, I think that 1.6 is the frequency of human steps or something
|
||||
walking_offset[axis][i] = math.sin(inp)*self.properties.wag.offset[axis][i]
|
||||
else
|
||||
local old_value = walking_offset[axis][i]
|
||||
if math.abs(walking_offset[axis][i]) > .005 then
|
||||
local multiplier = 1/props.wag.decay_speed
|
||||
walking_offset[axis][i] = walking_offset[axis][i]-(walking_offset[axis][i]*multiplier*dt)
|
||||
else
|
||||
walking_offset[axis][i] = 0
|
||||
end
|
||||
if math.abs(walking_offset[axis][i]) > math.abs(old_value) then
|
||||
walking_offset[axis][i] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local e = 2.7182818284590452353602874713527 --I don't know how to find it otherwise...
|
||||
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]
|
||||
local recoil_vel = Guns4d.math.clamp(self.velocities.recoil[axis][i],-self.properties.recoil.angular_velocity_max[axis],self.properties.recoil.angular_velocity_max[axis])
|
||||
local old_recoil_vel = recoil_vel
|
||||
recoil = recoil + recoil_vel
|
||||
--this is modelled off a geometric sequence where the Y incercept of the sequence is set to recoil_vel.
|
||||
if math.abs(recoil_vel) > 0.001 then
|
||||
local r = (10*self.properties.recoil.velocity_correction_factor[axis])^-1
|
||||
local vel_co = e^-( (self.time_since_last_fire^2)/(2*r^2) )
|
||||
recoil_vel = self.velocities.init_recoil[axis][i]*vel_co
|
||||
else
|
||||
recoil_vel = 0
|
||||
end
|
||||
self.velocities.recoil[axis][i] = recoil_vel
|
||||
|
||||
--ax^2+bx+c
|
||||
--recoil_velocity_correction_rate
|
||||
--recoil_correction_rate
|
||||
local old_recoil = recoil
|
||||
local abs = math.abs(recoil)
|
||||
local sign = old_recoil/abs
|
||||
if abs > 0.001 then
|
||||
local correction_value = abs*self.time_since_last_fire*self.properties.recoil.target_correction_factor[axis]
|
||||
correction_value = Guns4d.math.clamp(correction_value, 0, self.properties.recoil.target_correction_max_rate[axis])
|
||||
abs=abs-(correction_value*dt)
|
||||
--prevent overcorrection
|
||||
if abs < 0 then
|
||||
abs = 0
|
||||
end
|
||||
end
|
||||
if sign~=sign then
|
||||
sign = 1
|
||||
end
|
||||
self.offsets.recoil[axis][i] = abs*sign
|
||||
end
|
||||
end
|
||||
end
|
||||
function gun_default:update_animation(dt)
|
||||
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
|
||||
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.
|
||||
function gun_default:set_animation(frames, length, fps, loop)
|
||||
loop = loop or false --why the fuck default is true? I DONT FUCKIN KNOW (this undoes this)
|
||||
assert(type(frames)=="table" and frames.x and frames.y, "frames invalid or nil in set_animation()!")
|
||||
assert(not (length and fps), "cannot play animation with both specified length and specified fps. Only one parameter can be used.")
|
||||
local num_frames = math.abs(frames.x-frames.y)
|
||||
if length then
|
||||
fps = num_frames/length
|
||||
elseif not fps then
|
||||
fps = self.consts.DEFAULT_FPS
|
||||
end
|
||||
self.entity:set_animation(frames, fps, 0, loop) --see init.lua for modified ObjRef stuff.
|
||||
end
|
||||
function gun_default:clear_animation()
|
||||
local loaded = false
|
||||
if self.properties.ammo.magazine_only then
|
||||
if self.ammo_handler.ammo.loaded_mag ~= "empty" then
|
||||
loaded = true
|
||||
end
|
||||
elseif self.ammo_handler.ammo.total_bullets > 0 then
|
||||
loaded = true
|
||||
end
|
||||
if loaded then
|
||||
self.entity:set_animation({x=self.properties.visuals.animations.loaded.x, y=self.properties.visuals.animations.loaded.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
|
||||
else
|
||||
self.entity:set_animation({x=self.properties.visuals.animations.empty.x, y=self.properties.visuals.animations.empty.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
|
||||
end
|
||||
end
|
||||
local function adjust_gain(tbl, v)
|
||||
v = tbl.third_person_gain_multiplier or v
|
||||
for i = 1, #tbl do
|
||||
adjust_gain(tbl[i], v)
|
||||
end
|
||||
if tbl.gain and (tbl.split_audio_by_perspective~=false) then
|
||||
if type(tbl.gain) == "number" then
|
||||
tbl.gain = tbl.gain*v
|
||||
else
|
||||
tbl.gain.min = tbl.gain.min*v
|
||||
tbl.gain.max = tbl.gain.max*v
|
||||
end
|
||||
end
|
||||
end
|
||||
function gun_default:play_sounds(sound)
|
||||
local thpson_sound = Guns4d.table.deep_copy(sound)
|
||||
local fsprsn_sound = Guns4d.table.deep_copy(sound)
|
||||
|
||||
thpson_sound.pos = self.pos
|
||||
thpson_sound.player = self.player
|
||||
thpson_sound.exclude_player = self.player
|
||||
adjust_gain(thpson_sound, self.consts.THIRD_PERSON_GAIN_MULTIPLIER)
|
||||
|
||||
fsprsn_sound.player = self.player
|
||||
fsprsn_sound.to_player = "from_player"
|
||||
|
||||
return Guns4d.play_sounds(thpson_sound), Guns4d.play_sounds(fsprsn_sound)
|
||||
end
|
||||
function gun_default:update_breathing(dt)
|
||||
assert(self.instance)
|
||||
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.
|
||||
local x = (self.time_since_creation%breathing_info.rate)*math.pi/(breathing_info.rate-breathing_info.pause)
|
||||
local scale = self.properties.breathing_scale
|
||||
--now if it's above math.pi we know it's in the pause half of the cycle. For smoothness, we cut the sine off early and decay the value non-linearly.
|
||||
--not sure why 8/9 is a constant here... I assume it's if it's 8/9 of the way through the cycle. Not going to worry about it.
|
||||
if x > math.pi*(8/9) then
|
||||
self.offsets.breathing.player_axial.x=self.offsets.breathing.player_axial.x-(self.offsets.breathing.player_axial.x*2*dt)
|
||||
else
|
||||
self.offsets.breathing.player_axial.x = scale*(math.sin(x))
|
||||
end
|
||||
end
|
||||
|
||||
function gun_default:update_sway(dt)
|
||||
assert(self.instance, "attempt to call object method from a base class")
|
||||
local sprops = self.properties.sway
|
||||
for axis, sway in pairs(self.offsets.sway) do
|
||||
local sway_vel = self.velocities.sway[axis]
|
||||
local ran
|
||||
ran = Vec.apply(Vec.new(), function(i,v)
|
||||
if i ~= "x" then
|
||||
return (math.random()-.5)*2
|
||||
end
|
||||
end)
|
||||
ran.z = 0
|
||||
local vel_mul = self.multiplier_coefficient(sprops.hipfire_velocity_multiplier[axis], 1-self.control_handler.ads_location)
|
||||
sway_vel = Vec.normalize(sway_vel+(ran*dt))*sprops.angular_velocity[axis]*vel_mul
|
||||
sway=sway+(sway_vel*dt)
|
||||
local len_mul = self.multiplier_coefficient(sprops.hipfire_angle_multiplier[axis], 1-self.control_handler.ads_location)
|
||||
if Vec.length(sway) > sprops.max_angle[axis]*len_mul then
|
||||
sway=Vec.normalize(sway)*sprops.max_angle[axis]*len_mul
|
||||
sway_vel = Vec.new()
|
||||
end
|
||||
self.offsets.sway[axis] = sway
|
||||
self.velocities.sway[axis] = sway_vel
|
||||
end
|
||||
end
|
||||
|
||||
function gun_default:update_animation_rotation()
|
||||
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 out
|
||||
if self.b3d_model.global_frames.rotation then
|
||||
if self.b3d_model.global_frames.rotation[frame1] then
|
||||
if (not self.b3d_model.global_frames.rotation[frame2]) or (current_frame==frame1) then
|
||||
out = vector.new(self.b3d_model.global_frames.rotation[frame1]:to_euler_angles_unpack())*180/math.pi
|
||||
--print("rawsent")
|
||||
else --to stop nan
|
||||
local ip_ratio = (current_frame-frame1)/(frame2-frame1)
|
||||
local vec1 = self.b3d_model.global_frames.rotation[frame1]
|
||||
local vec2 = self.b3d_model.global_frames.rotation[frame2]
|
||||
out = vector.new(vec1:slerp(vec2, ip_ratio):to_euler_angles_unpack())*180/math.pi
|
||||
end
|
||||
else
|
||||
out = vector.copy(self.b3d_model.global_frames.rotation[1])
|
||||
end
|
||||
--print(frame1, frame2, current_frame, dump(out))
|
||||
else
|
||||
out = vector.new()
|
||||
end
|
||||
self.animation_rotation = out
|
||||
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+1
|
||||
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
|
||||
|
||||
for i, v in pairs(out) do
|
||||
if self.b3d_model.global_frames[i] then
|
||||
if self.b3d_model.global_frames[i][frame1] then
|
||||
if (not self.b3d_model.global_frames[i][frame2]) or (current_frame==frame1) then
|
||||
out[i] = vector.copy(self.b3d_model.global_frames[i][frame1])
|
||||
else --to stop nan
|
||||
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
|
||||
out[i]=vector.copy(self.b3d_model.global_frames[i][1])
|
||||
end
|
||||
else
|
||||
out[i] = vector.new()
|
||||
end
|
||||
end
|
||||
return out.arm_left, out.arm_right
|
||||
--return vector.copy(self.b3d_model.global_frames.arm_left[1]), vector.copy(self.b3d_model.global_frames.arm_right[1])
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
})
|
||||
|
||||
Guns4d.gun = gun_default
|
||||
dofile(minetest.get_modpath("guns4d").."/classes/gun_construct.lua")
|
||||
dofile(minetest.get_modpath("guns4d").."/classes/Gun-methods.lua")
|
||||
dofile(minetest.get_modpath("guns4d").."/classes/Gun-construct.lua")
|
||||
|
||||
gun_default.construct = function(def)
|
||||
if def.instance then
|
||||
|
@ -7,7 +7,7 @@ Guns4d.default_controls.aim = {
|
||||
conditions = {"RMB"},
|
||||
loop = false,
|
||||
timer = 0,
|
||||
func = function(active, interrupted, data, busy_list, gun, handler)
|
||||
func = function(self, active, interrupted, data, busy_list, gun, handler)
|
||||
if active then
|
||||
handler.control_handler.ads = not handler.control_handler.ads
|
||||
end
|
||||
@ -17,7 +17,7 @@ Guns4d.default_controls.auto = {
|
||||
conditions = {"LMB"},
|
||||
loop = true,
|
||||
timer = 0,
|
||||
func = function(active, interrupted, data, busy_list, gun, handler)
|
||||
func = function(self, active, interrupted, data, busy_list, gun, handler)
|
||||
if gun.properties.firemodes[gun.current_firemode] == "auto" then
|
||||
gun:attempt_fire()
|
||||
end
|
||||
@ -27,7 +27,7 @@ Guns4d.default_controls.firemode = {
|
||||
conditions = {"sneak", "zoom"},
|
||||
loop = false,
|
||||
timer = 0,
|
||||
func = function(active, interrupted, data, busy_list, gun, handler)
|
||||
func = function(self, active, interrupted, data, busy_list, gun, handler)
|
||||
if active then
|
||||
if not (busy_list.on_use or busy_list.auto) then
|
||||
gun:cycle_firemodes()
|
||||
@ -35,6 +35,7 @@ Guns4d.default_controls.firemode = {
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
--[[Guns4d.default_controls.toggle_safety = {
|
||||
conditions = {"sneak", "zoom"},
|
||||
loop = false,
|
||||
@ -49,7 +50,7 @@ Guns4d.default_controls.firemode = {
|
||||
end
|
||||
end
|
||||
}]]
|
||||
Guns4d.default_controls.on_use = function(itemstack, handler, pointed_thing, busy_list)
|
||||
Guns4d.default_controls.on_use = function(self, itemstack, handler, pointed_thing, busy_list)
|
||||
local gun = handler.gun
|
||||
local fmode = gun.properties.firemodes[gun.current_firemode]
|
||||
if fmode ~= "safe" and not (gun.burst_queue > 0) then
|
||||
@ -194,7 +195,7 @@ Guns4d.default_controls.reload = {
|
||||
mode = "hybrid",
|
||||
timer = 0, --1 so we have a call to initialize the timer. This will also mean that data.toggled and data.continue will need to be set manually
|
||||
--remember that the data table allows us to store arbitrary data
|
||||
func = function(active, interrupted, data, busy_list, gun, handler)
|
||||
func = function(self, active, interrupted, data, busy_list, gun, handler)
|
||||
local ammo_handler = gun.ammo_handler
|
||||
local props = gun.properties
|
||||
if active and not busy_list.firemode then
|
||||
|
@ -1,75 +0,0 @@
|
||||
local show_guide
|
||||
minetest.register_tool("guns4d:guide_book", {
|
||||
description = "mysterious gun related manual",
|
||||
inventory_image = "guns4d_guide.png",
|
||||
on_use = function(itemstack, player, pointed)
|
||||
show_guide(player,1)
|
||||
end,
|
||||
on_place = function(itemstack, player, pointed_thing)
|
||||
if pointed_thing and (pointed_thing.type == "node") then
|
||||
local pname = player:get_player_name()
|
||||
local node = minetest.get_node(pointed_thing.under).name
|
||||
local props = Guns4d.node_properties[node]
|
||||
if props.behavior~="ignore" then
|
||||
minetest.chat_send_player(pname, math.ceil(props.mmRHA).."mm of \"Rolled Homogenous Armor\" per meter")
|
||||
minetest.chat_send_player(pname, (math.ceil(props.random_deviation*100)/100).."° of deviation per meter")
|
||||
else
|
||||
minetest.chat_send_player(pname, "bullets pass through this block like air")
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
local pages = {
|
||||
--first page, diagram of m4 and controls
|
||||
"\
|
||||
size[7.5,10.5]\
|
||||
image[0,0;7.5,10.5;guns4d_guide_cover.png]\
|
||||
",
|
||||
"\
|
||||
size[15,10.5]\
|
||||
image[0,0;15,10.5;m4_diagram_text_en.png]\
|
||||
image[0,0;15,10.5;m4_diagram_overlay.png]\
|
||||
",
|
||||
"\
|
||||
size[15,10.5]\
|
||||
image[0,0;15,10.5;guns4d_guide_page_2.png]\
|
||||
"
|
||||
--
|
||||
}
|
||||
function show_guide(player, page)
|
||||
player:hud_set_flags({wielditem=false})
|
||||
local form = pages[page]
|
||||
form = "\
|
||||
formspec_version[6]\
|
||||
"..form
|
||||
if page==1 then
|
||||
form=form.."\
|
||||
button[5.5,9.5;.7,.5;page_next;next]"
|
||||
else
|
||||
form=form.."\
|
||||
image[0,0;15,10.5;page_crinkles.png]\
|
||||
button[13.75,9.75;.7,.5;page_next;next]\
|
||||
button[.6,9.75;.7,.5;page_back;back]\
|
||||
field[5.6,9.8;.7,.5;page_number;page;"..page.."]\
|
||||
field_close_on_enter[page_number;false]\
|
||||
label[6.25,10.05; /"..#pages.."]"
|
||||
end
|
||||
--button[<X>,<Y>;<W>,<H>;page_turn;<label>]\
|
||||
--field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]
|
||||
|
||||
minetest.show_formspec(player:get_player_name(), "guns4d:guide", form)
|
||||
end
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname == "guns4d:guide" then
|
||||
if (fields.page_number and tonumber(fields.page_number)) or not fields.page_number then
|
||||
fields.page_number = fields.page_number or 1
|
||||
local num = tonumber(fields.page_number)+((fields.page_next and 1) or (fields.page_back and -1) or 0)
|
||||
show_guide(player,
|
||||
(pages[num] and num) or ((num > 1) and #pages) or 1
|
||||
)
|
||||
end
|
||||
if fields.quit then
|
||||
player:hud_set_flags({wielditem=true})
|
||||
end
|
||||
end
|
||||
end)
|
@ -1,61 +0,0 @@
|
||||
|
||||
--register the infinite ammo privelage.
|
||||
minetest.register_privilege(Guns4d.config.infinite_ammo_priv, {
|
||||
description = "allows player to have infinite ammo.",
|
||||
give_to_singleplayer = false,
|
||||
on_grant = function(name, granter_name)
|
||||
local handler = Guns4d.players[name]
|
||||
handler.infinite_ammo = true
|
||||
minetest.chat_send_player(name, "infinite ammo enabled by "..(granter_name or "unknown"))
|
||||
if handler.gun then
|
||||
handler.gun:update_image_and_text_meta()
|
||||
end
|
||||
end,
|
||||
on_revoke = function(name, revoker_name)
|
||||
local handler = Guns4d.players[name]
|
||||
handler.infinite_ammo = false
|
||||
minetest.chat_send_player(name, "infinite ammo disabled by "..(revoker_name or "unknown"))
|
||||
if handler.gun then
|
||||
handler.gun:update_image_and_text_meta()
|
||||
end
|
||||
end,
|
||||
})
|
||||
minetest.register_chatcommand("ammoinf", {
|
||||
parameters = "player",
|
||||
description = "quick toggle infinite ammo",
|
||||
privs = {privs=true},
|
||||
func = function(caller, arg)
|
||||
local trgt
|
||||
local args = string.split(arg, " ")
|
||||
local set_arg
|
||||
if #args > 1 then
|
||||
trgt = args[1]
|
||||
set_arg = args[2]
|
||||
else
|
||||
set_arg = args[1]
|
||||
trgt = caller
|
||||
end
|
||||
local handler = Guns4d.players[trgt]
|
||||
local set_to
|
||||
if set_arg then
|
||||
if set_arg == "true" then
|
||||
set_to = true
|
||||
elseif set_arg ~= "false" then --if it's false we leave it as nil
|
||||
minetest.chat_send_player(caller, "cannot toggle ammoinf, invalid value:"..set_arg)
|
||||
return
|
||||
end
|
||||
else
|
||||
set_to = not handler.infinite_ammo --if it's false set it to nil, otherwise set it to true.
|
||||
if set_to == false then set_to = nil end
|
||||
end
|
||||
local privs = minetest.get_player_privs(trgt)
|
||||
privs[Guns4d.config.infinite_ammo_priv] = set_to
|
||||
minetest.set_player_privs(trgt, privs)
|
||||
minetest.chat_send_player(caller, "infinite ammo "..((set_to and "granted to") or "revoked from") .." user '"..trgt.."'")
|
||||
handler.infinite_ammo = set_to or false
|
||||
if handler.gun then
|
||||
handler.gun:update_image_and_text_meta()
|
||||
handler.player:set_wielded_item(handler.gun.itemstack)
|
||||
end
|
||||
end
|
||||
})
|
70
init.lua
70
init.lua
@ -29,6 +29,8 @@ Guns4d.config = {
|
||||
third_person_gain_multiplier = 1/3,
|
||||
default_penetration_iteration_distance = .25,
|
||||
maximum_bullet_holes = 20,
|
||||
inventory_listname = "main",
|
||||
aim_out_multiplier = 1.5,
|
||||
--enable_assert = false,
|
||||
realistic_items = false
|
||||
--`["official_content.replace_ads_with_bloom"] = false,
|
||||
@ -51,7 +53,6 @@ end
|
||||
minetest.rmdir(modpath.."/temp", true)
|
||||
minetest.mkdir(modpath.."/temp")
|
||||
|
||||
dofile(modpath.."/infinite_ammo.lua")
|
||||
dofile(modpath.."/misc_helpers.lua")
|
||||
dofile(modpath.."/item_entities.lua")
|
||||
dofile(modpath.."/play_sound.lua")
|
||||
@ -60,12 +61,13 @@ dofile(modpath.."/default_controls.lua")
|
||||
dofile(modpath.."/touch_support.lua")
|
||||
dofile(modpath.."/block_values.lua")
|
||||
dofile(modpath.."/ammo_api.lua")
|
||||
dofile(modpath.."/guide_book.lua")
|
||||
dofile(modpath.."/menus_and_guides.lua")
|
||||
local path = modpath .. "/classes"
|
||||
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.."/Sprite_scope.lua")
|
||||
dofile(path.."/Dynamic_crosshair.lua")
|
||||
dofile(path.."/Gun.lua") --> loads /classes/gun_construct.lua
|
||||
@ -76,10 +78,68 @@ dofile(path.."/Player_handler.lua")
|
||||
path = modpath .. "/models"
|
||||
dofile(path.."/3darmor/init.lua")
|
||||
|
||||
--infinite ammo
|
||||
minetest.register_privilege(Guns4d.config.infinite_ammo_priv, {
|
||||
description = "allows player to have infinite ammo.",
|
||||
give_to_singleplayer = false,
|
||||
on_grant = function(name, granter_name)
|
||||
local handler = Guns4d.players[name]
|
||||
handler.infinite_ammo = true
|
||||
minetest.chat_send_player(name, "infinite ammo enabled by "..(granter_name or "unknown"))
|
||||
if handler.gun then
|
||||
handler.gun:update_image_and_text_meta()
|
||||
end
|
||||
end,
|
||||
on_revoke = function(name, revoker_name)
|
||||
local handler = Guns4d.players[name]
|
||||
handler.infinite_ammo = false
|
||||
minetest.chat_send_player(name, "infinite ammo disabled by "..(revoker_name or "unknown"))
|
||||
if handler.gun then
|
||||
handler.gun:update_image_and_text_meta()
|
||||
end
|
||||
end,
|
||||
})
|
||||
minetest.register_chatcommand("ammoinf", {
|
||||
parameters = "player",
|
||||
description = "quick toggle infinite ammo",
|
||||
privs = {privs=true},
|
||||
func = function(caller, arg)
|
||||
local trgt
|
||||
local args = string.split(arg, " ")
|
||||
local set_arg
|
||||
if #args > 1 then
|
||||
trgt = args[1]
|
||||
set_arg = args[2]
|
||||
else
|
||||
set_arg = args[1]
|
||||
trgt = caller
|
||||
end
|
||||
local handler = Guns4d.players[trgt]
|
||||
local set_to
|
||||
if set_arg then
|
||||
if set_arg == "true" then
|
||||
set_to = true
|
||||
elseif set_arg ~= "false" then --if it's false we leave it as nil
|
||||
minetest.chat_send_player(caller, "cannot toggle ammoinf, invalid value:"..set_arg)
|
||||
return
|
||||
end
|
||||
else
|
||||
set_to = not handler.infinite_ammo --if it's false set it to nil, otherwise set it to true.
|
||||
if set_to == false then set_to = nil end
|
||||
end
|
||||
local privs = minetest.get_player_privs(trgt)
|
||||
privs[Guns4d.config.infinite_ammo_priv] = set_to
|
||||
minetest.set_player_privs(trgt, privs)
|
||||
minetest.chat_send_player(caller, "infinite ammo "..((set_to and "granted to") or "revoked from") .." user '"..trgt.."'")
|
||||
handler.infinite_ammo = set_to or false
|
||||
if handler.gun then
|
||||
handler.gun:update_image_and_text_meta()
|
||||
handler.player:set_wielded_item(handler.gun.itemstack)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
--load after
|
||||
path = minetest.get_modpath("guns4d")
|
||||
|
||||
--player handling
|
||||
local player_handler = Guns4d.player_handler
|
||||
local objref_mtable
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
|
@ -46,7 +46,6 @@ def.set_item = function(self, item)
|
||||
return
|
||||
end
|
||||
|
||||
local item_def = Guns4d.registered_items[stack:get_name()]
|
||||
--[[local a = item_def.collisionbox_size
|
||||
local o = item_def.collisionbox_offset
|
||||
local b = item_def.selectionbox
|
||||
@ -57,6 +56,7 @@ def.set_item = function(self, item)
|
||||
cbox = {(-a-o.x)/20, (-a-o.y)/20, (-a-o.z)/20, (a-o.x)/20, (a-o.y)/20, (a-o.z)/20}
|
||||
sbox = {(-b.x-o.x)/20, (-b.y-o.y)/20, (-b.z-o.z)/20, (b.x-o.x)/20, (b.y-o.y)/20, (b.z-o.z)/20}
|
||||
end]]
|
||||
local item_def = Guns4d.registered_items[stack:get_name()]
|
||||
local cbox = item_def.collisionbox
|
||||
local sbox = item_def.selectionbox
|
||||
self.object:set_properties({
|
||||
@ -105,7 +105,7 @@ def.on_step = function(self, dt, mr, ...)
|
||||
self._4dguns_rotated = true
|
||||
else
|
||||
self.object:set_properties({
|
||||
automatic_rotate = math.pi * 0.5 * 0.2 / item_def.visual_size,
|
||||
automatic_rotate = math.pi * 0.5 * 0.2,
|
||||
})
|
||||
local rot = self.object:get_rotation()
|
||||
self.object:set_rotation({y=rot.y, x=0, z=0})
|
||||
|
195
menus_and_guides.lua
Normal file
195
menus_and_guides.lua
Normal file
@ -0,0 +1,195 @@
|
||||
local guide_players_wielditem = {}
|
||||
minetest.register_tool("guns4d:guide_book", {
|
||||
description = "mysterious gun related manual",
|
||||
inventory_image = "guns4d_guide.png",
|
||||
on_use = function(itemstack, player, pointed)
|
||||
local hud_flags = player:hud_get_flags()
|
||||
guide_players_wielditem[player]=hud_flags.wielditem
|
||||
Guns4d.show_guide(player,1)
|
||||
end,
|
||||
on_place = function(itemstack, player, pointed_thing)
|
||||
if pointed_thing and (pointed_thing.type == "node") then
|
||||
local pname = player:get_player_name()
|
||||
local node = minetest.get_node(pointed_thing.under).name
|
||||
local props = Guns4d.node_properties[node]
|
||||
if props.behavior~="ignore" then
|
||||
minetest.chat_send_player(pname, math.ceil(props.mmRHA).."mm of \"Rolled Homogenous Armor\" per meter")
|
||||
minetest.chat_send_player(pname, (math.ceil(props.random_deviation*100)/100).."° of deviation per meter")
|
||||
else
|
||||
minetest.chat_send_player(pname, "bullets pass through this block like air")
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
local pages = {
|
||||
--first page, diagram of m4 and controls
|
||||
"\
|
||||
size[7.5,10.5]\
|
||||
image[0,0;7.5,10.5;guns4d_guide_cover.png]\
|
||||
",
|
||||
"\
|
||||
size[15,10.5]\
|
||||
image[0,0;15,10.5;m4_diagram_text_en.png]\
|
||||
image[0,0;15,10.5;m4_diagram_overlay.png]\
|
||||
",
|
||||
"\
|
||||
size[15,10.5]\
|
||||
image[0,0;15,10.5;guns4d_guide_page_2.png]\
|
||||
"
|
||||
--
|
||||
}
|
||||
function Guns4d.show_guide(player, page)
|
||||
player:hud_set_flags({wielditem=false})
|
||||
local form = pages[page]
|
||||
form = "\
|
||||
formspec_version[6]\
|
||||
"..form
|
||||
if page==1 then
|
||||
form=form.."\
|
||||
button[5.5,9.5;.7,.5;page_next;next]"
|
||||
else
|
||||
form=form.."\
|
||||
image[0,0;15,10.5;page_crinkles.png]\
|
||||
button[13.75,9.75;.7,.5;page_next;next]\
|
||||
button[.6,9.75;.7,.5;page_back;back]\
|
||||
field[5.6,9.8;.7,.5;page_number;page;"..page.."]\
|
||||
field_close_on_enter[page_number;false]\
|
||||
label[6.25,10.05; /"..#pages.."]"
|
||||
end
|
||||
--button[<X>,<Y>;<W>,<H>;page_turn;<label>]\
|
||||
--field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]
|
||||
|
||||
minetest.show_formspec(player:get_player_name(), "guns4d:guide", form)
|
||||
end
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname == "guns4d:guide" then
|
||||
if (fields.page_number and tonumber(fields.page_number)) or not fields.page_number then
|
||||
fields.page_number = fields.page_number or 1
|
||||
local num = tonumber(fields.page_number)+((fields.page_next and 1) or (fields.page_back and -1) or 0)
|
||||
Guns4d.show_guide(player,
|
||||
(pages[num] and num) or ((num > 1) and #pages) or 1
|
||||
)
|
||||
end
|
||||
if fields.quit then
|
||||
player:hud_set_flags({wielditem=guide_players_wielditem[player]})
|
||||
guide_players_wielditem[player]=nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
minetest.register_chatcommand("guns4d_guide", {
|
||||
description = "open the Guns4d guide book",
|
||||
func = function(pname, arg)
|
||||
local player = minetest.get_player_by_name(pname)
|
||||
local flags = player:hud_get_flags()
|
||||
guide_players_wielditem[player]=flags.wielditem
|
||||
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)
|
||||
else
|
||||
minetest.chat_send_player(pname, "cannot show the inventory menu for a gun which is not help")
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
|
@ -8,6 +8,100 @@ Guns4d.table = {}
|
||||
Guns4d.unique_id = {
|
||||
generated = {},
|
||||
}
|
||||
|
||||
--[[format of field modifiers
|
||||
{
|
||||
int_field = { --the field is an integer
|
||||
add = .1 --add .1 to the field (after multiplying)
|
||||
mul = 2 --multipy before adding
|
||||
},
|
||||
int_field_2 = {
|
||||
override = 4 --sets the field to 4
|
||||
override_priority = 2 --if others set and have a higher priority, this will be it's priority
|
||||
remove = false --true if you want to remove it
|
||||
}
|
||||
table_field = {
|
||||
int_field = {. . .}
|
||||
}
|
||||
}
|
||||
]]
|
||||
function Guns4d.apply_field_modifiers(props, mods)
|
||||
local out_props = {}
|
||||
for i, v in pairs(props) do
|
||||
if type(v)=="number" then
|
||||
local add = 0
|
||||
local mul = 1
|
||||
local override
|
||||
local remove = false
|
||||
local priority = math.huge
|
||||
for _, modifier in ipairs(mods) do
|
||||
local a = modifier[i]
|
||||
if a then
|
||||
add = add + (a.add or 0)
|
||||
mul = mul * (a.mul or 1)
|
||||
if a.override and (priority > (a.priority or 10)) then
|
||||
override = a.override
|
||||
priority = a.priority or 10
|
||||
end
|
||||
remove = a.remove
|
||||
end
|
||||
end
|
||||
out_props[i] = (((override or v) or 0)*mul)+add
|
||||
if remove then
|
||||
out_props[i] = nil
|
||||
end
|
||||
elseif type(v)=="table" then
|
||||
for _, modifier in pairs(mods) do
|
||||
local a = modifier[i]
|
||||
Guns4d.apply_field_modifiers(v, a)
|
||||
end
|
||||
else
|
||||
local override
|
||||
local priority = math.huge
|
||||
local remove
|
||||
for _, modifier in ipairs(mods) do
|
||||
local a = modifier[i]
|
||||
if type(v)==type(a.override) then
|
||||
if a.override and (priority > (a.priority or 10)) then
|
||||
override = a.override
|
||||
priority = a.priority or 10
|
||||
end
|
||||
remove = a.remove
|
||||
if a.remove then
|
||||
out_props[i]=nil
|
||||
end
|
||||
elseif a then
|
||||
minetest.log("error", "modifier name: "..(modifier._modifier_name or "???").."attempted to override a "..type(v).." with a "..type(v).." value")
|
||||
end
|
||||
end
|
||||
out_props[i] = ((override~=nil) and override) or out_props[i]
|
||||
if remove then
|
||||
out_props[i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
return out_props
|
||||
end
|
||||
print(dump(Guns4d.apply_field_modifiers({
|
||||
a=0,
|
||||
y=1,
|
||||
z=10,
|
||||
st="string"
|
||||
}, {
|
||||
a={
|
||||
add=1,
|
||||
mul=2
|
||||
},
|
||||
z={
|
||||
mul=2,
|
||||
add=1
|
||||
},
|
||||
st={
|
||||
override=10
|
||||
}
|
||||
}
|
||||
)))
|
||||
|
||||
function Guns4d.unique_id.generate()
|
||||
local genned_ids = Guns4d.unique_id.generated
|
||||
local id = string.sub(tostring(math.random()), 3)
|
||||
|
BIN
textures/m4_ortho.png
Normal file
BIN
textures/m4_ortho.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
Loading…
x
Reference in New Issue
Block a user