been awhile since I pushed, not sure what I added

This commit is contained in:
FatalErr42O 2023-10-03 11:00:30 -07:00
parent 6ec442ee31
commit e80a4a4242
16 changed files with 593 additions and 334 deletions

View File

@ -43,10 +43,13 @@ function Ammo_handler:spend_round()
local meta = self.gun.meta
--subtract the bullet
if self.ammo.total_bullets > 0 then
self.ammo.loaded_bullets[bullet_spent] = self.ammo.loaded_bullets[bullet_spent]-1
if self.ammo.loaded_bullets[bullet_spent] == 0 then self.ammo.loaded_bullets[bullet_spent] = nil end
self.ammo.total_bullets = self.ammo.total_bullets - 1
--set the new current bullet
--only actually subtract the round if INFINITE_AMMO_IN_CREATIVE isnt true or they arent creative.
if not (self.gun.consts.INFINITE_AMMO_IN_CREATIVE and minetest.check_player_privs(self.gun.player, "creative")) then
self.ammo.loaded_bullets[bullet_spent] = self.ammo.loaded_bullets[bullet_spent]-1
if self.ammo.loaded_bullets[bullet_spent] == 0 then self.ammo.loaded_bullets[bullet_spent] = nil end
self.ammo.total_bullets = self.ammo.total_bullets - 1
end
--set the new current bullet
if next(self.ammo.loaded_bullets) then
self.ammo.next_bullet = math.weighted_randoms(self.ammo.loaded_bullets)
meta:set_string("guns4d_next_bullet", self.ammo.next_bullet)
@ -66,7 +69,7 @@ function Ammo_handler:load_magazine()
local highest_ammo = -1
local gun = self.gun
local gun_accepts = gun.accepted_magazines
if self.ammo.loaded_mag ~= "empty" then
if self.ammo.loaded_mag ~= "empty" or self.ammo.total_bullets > 0 then
--it's undefined, make assumptions.
self:unload_all()
end

View File

@ -209,8 +209,8 @@ function ray:apply_damage(object, sharp_pen, blunt_pen)
local sharp_dmg = self.raw_sharp_damage*sharp_ratio
local hp = (object:get_hp()-blunt_dmg)-sharp_dmg
print(blunt_dmg, sharp_dmg, blunt_ratio, sharp_ratio)
print(self.blunt_penetration, self.sharp_penetration)
--print(blunt_dmg, sharp_dmg, blunt_ratio, sharp_ratio)
--print(self.blunt_penetration, self.sharp_penetration)
if hp < 0 then hp = 0 end
object:set_hp(hp, {type="set_hp", from="guns4d"})

View File

@ -0,0 +1,105 @@
local Dynamic_crosshair = Instantiatable_class:inherit({
increments = 1, --the number of pixels the reticle moves per frame.
frames = 32, --this defines the length of the sprite sheet. But it also helps us know how wide it is (since we have increments.)
image = "dynamic_crosshair_circular.png",
scale = 3,
normalize_walking = true,
normalize_breathing = true,
normalize_sway = true,
old_walking_vec = vector.new(),
construct = function(def)
if def.instance then
assert(def.gun, "no gun instance provided")
def.player = def.gun.player
def.handler = def.gun.handler
def.width = def.frames/def.increments
def.hud = def.player:hud_add{
hud_elem_type = "image",
position = {x=.5,y=.5},
scale = {x=def.scale,y=def.scale},
text = def.image.."^[verticalframe:"..def.frames..":0",
}
end
end
})
Guns4d.dynamic_crosshair = Dynamic_crosshair
local function absolute_vector(v)
return {x=math.abs(v.x), y=math.abs(v.y), z=math.abs(v.z)}
end
--really wish there was a better way to do this.
local function render_length(rotation, fov)
local dir = vector.rotate({x=0,y=0,z=1}, {x=rotation.x*math.pi/180,y=0,z=0})
vector.rotate(dir,{x=0,y=rotation.y*math.pi/180,z=0})
local out = Point_to_hud(dir, fov, 1)
return math.sqrt(out.x^2+out.y^2)
end
function Dynamic_crosshair:update(dt)
assert(self.instance, "attemptr to call object method on a class")
local handler = self.handler
local gun = self.gun
if handler.wininfo and not handler.control_bools.ads then
local fov = self.player:get_fov()
--we have to recalc the rough direction, otherwise walking will look wonky.
local temp_vector = vector.new()
for offset, v in pairs(gun.offsets) do
if (offset ~= "walking" or not self.normalize_walking) and (offset ~= "breathing" or not self.normalize_breathing) and (offset ~= "sway" or not self.normalize_sway) then
print(offset)
temp_vector = temp_vector + absolute_vector(v.player_axial) + absolute_vector(v.gun_axial)
end
end
if gun.consts.HAS_SWAY and self.normalize_sway then
local max_angle =
gun.properties.sway.max_angle.gun_axial*gun.multiplier_coefficient(gun.properties.sway.hipfire_angle_multiplier.gun_axial, 1-handler.ads_location)
+ gun.properties.sway.max_angle.player_axial*gun.multiplier_coefficient(gun.properties.sway.hipfire_angle_multiplier.player_axial, 1-handler.ads_location)
temp_vector = temp_vector + {x=max_angle, y=max_angle, z=0}
end
--make breathing just add to the overall rotation vector (as it could be in that circle at any time, and it looks better and is more fitting)
if gun.consts.HAS_BREATHING and self.normalize_breathing then
temp_vector = temp_vector + {x=gun.properties.breathing_scale, y=0, z=0}
end
--stop wag from looking wierd so the offset only expands and doesnt do a weird pulsing thing. Inefficient, hopefully not a terrible deal, taking the advice not to prematurely optimize.
local walking_vec = gun.offsets.walking.gun_axial + gun.offsets.walking.player_axial
if gun.consts.HAS_WAG and self.normalize_walking then
if handler.walking then --"velocity" is used to track velocity of the player for after movement effects. When they are no longer needed it is expunged, also indicating to the function its over
--only accept higher values for animation
if render_length(walking_vec, fov) > render_length(self.old_walking_vec, fov) then
self.old_walking_vec = vector.copy(walking_vec)
temp_vector = temp_vector + absolute_vector(walking_vec)
else
temp_vector = temp_vector + absolute_vector(self.old_walking_vec)
end
else
--only accept lower values for animation
if render_length(walking_vec, fov) < render_length(self.old_walking_vec, fov) then
self.old_walking_vec = walking_vec
temp_vector = temp_vector + absolute_vector(walking_vec)
else
temp_vector = temp_vector + absolute_vector(self.old_walking_vec)
end
end
end
--create a new dir using our parameters.
local dir = vector.rotate({x=0,y=0,z=1}, {x=temp_vector.x*math.pi/180, y=0, z=0})
dir = vector.rotate(dir, {x=0, y=temp_vector.y*math.pi/180, z=0})
--now figure out what frame will be our correct spread
local offset = Point_to_hud(dir, fov, 1) --pretend it's a 1:1 ratio so we can do things correctly.
local length = math.sqrt(offset.x^2+offset.y^2) --get the max length.
local img_perc = (self.scale*2*handler.wininfo.real_hud_scaling*self.width)/handler.wininfo.size.x --the percentage that the hud element takes up
local frame = length/img_perc --the percentage of the size the length takes up.
frame = math.floor(self.frames*frame)
frame = math.clamp(frame, 0, self.frames-1)
--"^[vertical_frame:"..self.frames..":"..frame
self.player:hud_change(self.hud, "text", self.image.."^[verticalframe:"..self.frames..":"..frame)
else
self.player:hud_change(self.hud, "text", "blank.png")
end
end
function Dynamic_crosshair:prepare_deletion()
self.player:hud_remove(self.hud)
end

View File

@ -9,6 +9,8 @@ local gun_default = {
properties = {
hip = { --used by gun entity (attached offset)
offset = Vec.new(),
sway_vel_mul = 5, --these are multipliers for various attributes. Does support fractional vals (which can be useful if you want to make hipfire more random with spread.)
sway_angle_mul = 1,
},
ads = { --used by player_handler, animation handler (eye bone offset from horizontal_offset), gun entity (attached offset)
offset = Vec.new(),
@ -28,18 +30,22 @@ local gun_default = {
gun_axial = 1,
player_axial = 1,
},
angular_velocity = {
angular_velocity = { --the velocity added per shot. This is the real "recoil" part of the recoil
gun_axial = {x=0, y=0},
player_axial = {x=0, y=0},
},
angular_velocity_bias = {
bias = { --dictates the distribution bias for the direction angular_velocity is in. I.e. if you want recoil to always go up you set x to 1, more often to the right? y to -.5
gun_axial = {x=1, y=0},
player_axial = {x=1, y=0},
},
target_correction_max_rate = { --the cap for time_since_fire*target_correction_factor
target_correction_max_rate = { --the cap for target_correction_fire (i.e. this is the maximum amount it will ever correct per second.)
gun_axial = 1,
player_axial = 1,
},
hipfire_multiplier = { --the mutliplier for recoil (angular_velocity) at hipfire (can be fractional)
gun_axial = 1,
player_axial = 1
},
},
sway = { --used by update_sway()
max_angle = {
@ -50,15 +56,24 @@ local gun_default = {
gun_axial = 0,
player_axial = 0,
},
hipfire_angle_multiplier = { --the mutliplier for sway max_angle at hipfire (can be fractional)
gun_axial = 2,
player_axial = 2
},
hipfire_velocity_multiplier = { --same as above but for velocity.
gun_axial = 2,
player_axial = 2
}
},
walking_offset = { --used by update_walking() (or something)
gun_axial = {x=1, y=-1},
player_axial = {x=1,y=1},
},
breathing_scale = .5, --the max angluler offset caused by breathing.
controls = { --used by control_handler
__overfill=true, --if present, this table will not be filled in.
aim = Guns4d.default_controls.aim,
--fire = Guns4d.default_controls.fire,
fire = Guns4d.default_controls.fire,
reload = Guns4d.default_controls.reload,
on_use = Guns4d.default_controls.on_use
},
@ -72,9 +87,14 @@ local gun_default = {
accepted_bullets = {},
accepted_magazines = {}
},
animations = { --used by animations handler for idle, and default controls
empty = {x=0,y=0},
loaded = {x=1,y=1},
visuals = {
--mesh
arm_right = "right_aimpoint",
arm_left = "left_aimpoint",
animations = { --used by animations handler for idle, and default controls
empty = {x=0,y=0},
loaded = {x=1,y=1},
},
},
--used by ammo_handler
flash_offset = Vec.new(), --used by fire() (for fsx and ray start pos) [RENAME NEEDED]
@ -82,12 +102,6 @@ local gun_default = {
ammo_handler = Ammo_handler
},
offsets = {
player_rotation = Vec.new(),
--I'll need all three of them, do some precalculation.
total_offset_rotation = {
gun_axial = Vec.new(),
player_axial = Vec.new(),
},
recoil = {
gun_axial = Vec.new(),
player_axial = Vec.new(),
@ -99,12 +113,22 @@ local gun_default = {
walking = {
gun_axial = Vec.new(),
player_axial = Vec.new(),
tick = 0,
--velocity
},
breathing = {
gun_axial = 1,
player_axial = 1,
gun_axial = Vec.new(), --gun axial unimplemented...
player_axial = Vec.new(),
}
},
spread = {
},
total_offset_rotation = { --can't be in offsets, as they're added automatically.
gun_axial = Vec.new(),
player_axial = Vec.new(),
},
player_rotation = Vec.new(),
velocities = {
recoil = {
gun_axial = Vec.new(),
@ -119,22 +143,31 @@ local gun_default = {
consts = {
HIP_PLAYER_GUN_ROT_RATIO = .75,
AIM_OUT_AIM_IN_SPEED_RATIO = 2.5,
HIPFIRE_BONE = "guns3d_hipfire_bone",
HIPFIRE_BONE = "guns3d_hipfire_bone", --these shouldn't be here at all, these need to be model determinant.
AIMING_BONE = "guns3d_aiming_bone",
KEYFRAME_SAMPLE_PRECISION = 1, --[[what frequency to take precalcualted keyframe samples. The lower this is the higher the memory allocation it will need- though minimal.
This will fuck shit up if you change it after gun construction/inheritence (interpolation between precalculated vectors will not work right)]]
HAS_RECOIL = true,
HAS_BREATHING = true,
HAS_SWAY = true,
HAS_WAG = true,
WAG_CYCLE_SPEED = 1.6,
WAG_DECAY = 1, --divisions per second
HAS_GUN_AXIAL_OFFSETS = true,
INFINITE_AMMO_IN_CREATIVE = true,
DEFAULT_FPS = 20,
DEFAULT_FPS = 60,
LOOP_IDLE_ANIM = false
},
animation_data = { --where animations data is stored.
anim_runtime = 0,
length = 0,
fps = 0,
animations_frames = {0,0},
frames = {0,0},
current_frame = 0,
--[[animations = {
}
]]
},
particle_spawners = {},
walking_tick = 0,
@ -143,25 +176,24 @@ local gun_default = {
rechamber_time = 0,
muzzle_flash = Guns4d.effects.muzzle_flash
}
function gun_default.multiplier_coefficient(multiplier, ratio)
return 1+((multiplier*ratio)-ratio)
end
--update the gun, da meat and da potatoes
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
local look_rotation = {x=handler.look_rotation.x,y=handler.look_rotation.y}
local total_rot = self.offsets.total_offset_rotation
local player_rot = self.offsets.player_rotation
local constant = 6
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 total_rot = self.total_offset_rotation
local player_rot = self.player_rotation
local constant = 5 --make this a config setting
--player look rotation. I'm going to keep it real, I don't remember what this equation does.
if not self.sprite_scope then
local next_vert_aim = ((player_rot.x+look_rotation.x)/(1+constant*dt))-look_rotation.x
if math.abs(look_rotation.x-next_vert_aim) > .005 then
player_rot.x = next_vert_aim
else
player_rot.x = -look_rotation.x
end
--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
player_rot.y = -handler.look_rotation.y
local next_vert_aim = ((player_rot.x+look_rotation.x)/(1+constant*dt))-look_rotation.x
if math.abs(look_rotation.x-next_vert_aim) > .005 then
player_rot.x = next_vert_aim
else
player_rot.x = -look_rotation.x
end
@ -179,37 +211,44 @@ function gun_default:update(dt)
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
--dynamic crosshair needs to be updated BEFORE wag
if self.properties.sprite_scope then
self.sprite_scope:update()
end
if self.properties.crosshair then
self.crosshair:update()
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.pos = self:get_pos()+self.handler:get_pos()
local bone, quat = self.model_handler:get_bone_global("Magazine")
minetest.chat_send_all(dump(bone))
--minetest.chat_send_all(dump(quat))
self:get_pos(bone/10, true)
--sprite scope
if self.properties.sprite_scope then
self.sprite_scope:update()
end
player_rot.y = -handler.look_rotation.y
local offsets = self.offsets
total_rot.player_axial = offsets.recoil.player_axial + offsets.walking.player_axial + offsets.sway.player_axial + {x=offsets.breathing.player_axial,y=0,z=0}
total_rot.gun_axial = offsets.recoil.gun_axial + offsets.walking.gun_axial + offsets.sway.gun_axial
--local player_axial = offsets.recoil.player_axial + offsets.walking.player_axial + offsets.sway.player_axial + offsets.breathing.player_axial
--local gun_axial = offsets.recoil.gun_axial + offsets.walking.gun_axial + offsets.sway.gun_axial
--apply the offsets.
total_rot.player_axial.x = 0; total_rot.player_axial.y = 0
total_rot.gun_axial.x = 0; total_rot.gun_axial.y = 0
for type, _ in pairs(total_rot) do
for i, offset in pairs(offsets) do
if self.consts.HAS_GUN_AXIAL_OFFSETS or type~="gun_axial" then
total_rot[type] = total_rot[type]+offset[type]
end
end
end
end
function gun_default:attempt_fire()
assert(self.instance, "attempt to call object method on a class")
if self.rechamber_time <= 0 then
local spent_bullet = self.ammo_handler:spend_round()
if spent_bullet then
if spent_bullet and spent_bullet ~= "empty" then
local dir = self.dir
local pos = self.pos
--[[print(dump(Guns4d.ammo.registered_bullets))
print(self.ammo_handler.next_bullet)
print(Guns4d.ammo.registered_bullets[self.ammo_handler.next_bullet])]]
local bullet_def = table.fill(Guns4d.ammo.registered_bullets[spent_bullet], {
player = self.player,
pos = pos,
@ -217,6 +256,10 @@ function gun_default:attempt_fire()
gun = self
})
Guns4d.bullet_ray:new(bullet_def)
if self.properties.visuals.animations.fire then
minetest.chat_send_all(dump(self.properties.visuals.animations.fire))
self:set_animation(self.properties.visuals.animations.fire, nil, false)
end
self:recoil()
self:muzzle_flash()
self.rechamber_time = 60/self.properties.firerateRPM
@ -226,9 +269,12 @@ 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] + (self.properties.recoil.angular_velocity[axis][i]*math.rand_sign((self.properties.recoil.angular_velocity_bias[axis][i]/2)+.5))
recoil[i] = recoil[i] + (rprops.angular_velocity[axis][i]
*math.rand_sign((rprops.bias[axis][i]/2)+.5))
*self.multiplier_coefficient(rprops.hipfire_multiplier[axis], 1-self.handler.ads_location)
end
end
self.time_since_last_fire = 0
@ -236,21 +282,26 @@ end
--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.offsets.total_offset_rotation
local rotation = self.total_offset_rotation
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 player = self.player
local rotation = self.offsets.total_offset_rotation
local handler = self.handler
local rotation = self.total_offset_rotation
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.player_axial.x)*math.pi/180), z=0}))
dir = Vec.rotate(dir, {y=((rotation.player_axial.y)*math.pi/180), x=0, z=0})
if not rltv then
dir = Vec.rotate(dir, {x=self.offsets.player_rotation.x*math.pi/180,y=self.offsets.player_rotation.y*math.pi/180,z=0})
if (self.properties.sprite_scope and handler.control_bools.ads) or (self.properties.crosshair and not handler.control_bools.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
--[[local hud_pos = Vec.rotate(dir, {x=0,y=self.offsets.player_rotation.y*math.pi/180,z=0})+player:get_pos()+{x=0,y=player:get_properties().eye_height,z=0}+vector.rotate(player:get_eye_offset()/10, {x=0,y=self.offsets.player_rotation.y*math.pi/180,z=0})
--[[local hud_pos = Vec.rotate(dir, {x=0,y=self.player_rotation.y*math.pi/180,z=0})+player:get_pos()+{x=0,y=player:get_properties().eye_height,z=0}+vector.rotate(player:get_eye_offset()/10, {x=0,y=self.player_rotation.y*math.pi/180,z=0})
local hud = player:hud_add({
hud_elem_type = "image_waypoint",
text = "muzzle_flash2.png",
@ -266,13 +317,20 @@ function gun_default:get_player_axial_dir(rltv)
end
function gun_default:get_dir(rltv)
assert(self.instance, "attempt to call object method on a class")
local rotation = self.offsets.total_offset_rotation
local rotation = self.total_offset_rotation
local handler = self.handler
--rotate x and then y.
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.gun_axial.x+rotation.player_axial.x)*math.pi/180), z=0}))
dir = Vec.rotate(dir, {y=((rotation.gun_axial.y+rotation.player_axial.y)*math.pi/180), x=0, z=0})
--for it to be relative the the camera, rotation by player look occours post.
if not rltv then
dir = Vec.rotate(dir, {x=self.offsets.player_rotation.x*math.pi/180,y=self.offsets.player_rotation.y*math.pi/180,z=0})
if (self.properties.sprite_scope and handler.control_bools.ads) or (self.properties.crosshair and not handler.control_bools.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
--local hud_pos = dir+player:get_pos()+{x=0,y=player:get_properties().eye_height,z=0}+vector.rotate(player:get_eye_offset()/10, {x=0,y=player_rotation.y*math.pi/180,z=0})
--[[local hud = player:hud_add({
hud_elem_type = "image_waypoint",
@ -289,7 +347,7 @@ function gun_default:get_dir(rltv)
end
--broken! doesn't properly reflect values.
function gun_default:get_pos(added_pos, debug)
function gun_default:get_pos(added_pos, relative, debug)
assert(self.instance, "attempt to call object method on a class")
local player = self.player
local handler = self.handler
@ -297,14 +355,14 @@ function gun_default:get_pos(added_pos, debug)
local gun_offset
if handler.control_bools.ads then
gun_offset = self.properties.ads.offset
bone_location, _ = player:get_eye_offset() or vector.zero(), nil
bone_location = player:get_eye_offset() or vector.zero()
bone_location.y = bone_location.y + handler:get_properties().eye_height
bone_location.x = handler.horizontal_offset
else
--minetest is really wacky.
gun_offset = self.properties.hip.offset
bone_location = handler.model_handler.offsets.arm.right
bone_location.x = -bone_location.x / 10
bone_location = vector.new(handler.player_model_handler.offsets.global.hipfire)
bone_location.x = bone_location.x / 10
bone_location.z = bone_location.z / 10
bone_location.y = bone_location.y / 10
end
@ -312,11 +370,20 @@ function gun_default:get_pos(added_pos, debug)
gun_offset = gun_offset+added_pos
end
--dir needs to be rotated twice seperately to avoid weirdness
local rotation = self.offsets.total_offset_rotation
local pos = Vec.rotate(bone_location, {x=0, y=-handler.look_rotation.y*math.pi/180, z=0})
pos = pos+Vec.rotate(gun_offset, Vec.dir_to_rotation(self.paxial_dir))
local hud_pos = pos+handler:get_pos()
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_offset, Vec.dir_to_rotation(self.paxial_dir))
else
pos = Vec.rotate(gun_offset, Vec.dir_to_rotation(self.local_paxial_dir)+{x=self.player_rotation.x*math.pi/180,y=0,z=0})+bone_location
end
if debug then
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
local hud = player:hud_add({
hud_elem_type = "image_waypoint",
text = "muzzle_flash2.png",
@ -350,27 +417,43 @@ function gun_default:has_entity()
end
function gun_default:update_wag(dt)
local handler = self.handler
local wag = self.offsets.walking
local velocity = wag.velocity
local old_tick
if handler.walking then
self.walking_tick = self.walking_tick + (dt*Vec.length(self.player:get_velocity()))
else
self.walking_tick = 0
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
for _, i in pairs({"x","y"}) do
for axis, _ in pairs(walking_offset) do
if handler.walking then
local time = self.walking_tick
if velocity and (not handler.walking) and (math.ceil(old_tick/self.consts.WAG_CYCLE_SPEED)+.5 < (math.ceil(wag.tick/self.consts.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
walking_offset[axis][i] = math.sin((time/1.6)*math.pi*multiplier)*self.properties.walking_offset[axis][i]
--if the result is negative we know that it's flipped, and thus can be ended.
local inp = (wag.tick/self.consts.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.walking_offset[axis][i]
else
local old_value = walking_offset[axis][i]
if (math.abs(walking_offset[axis][i]) > .5 and axis=="player_axial") or (math.abs(walking_offset[axis][i]) > .6 and axis=="gun_axial") then
local multiplier = (walking_offset[axis][i]/math.abs(walking_offset[axis][i]))
walking_offset[axis][i] = walking_offset[axis][i]-(dt*2*multiplier)
elseif axis == "gun_axial" then
if math.abs(walking_offset[axis][i]) > .005 then
local multiplier = 1/self.consts.WAG_DECAY
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
@ -417,42 +500,27 @@ function gun_default:update_recoil(dt)
end
end
end
local animation_data = {
anim_runtime = 0,
length = 0,
fps = 0,
animations_frames = {0,0},
current_frame = 0,
}
function gun_default:animation_update(dt)
function gun_default:update_animation(dt)
local ent = self.entity
local data = self.animation_data
local anim_range, frame_speed, frame_blend, frame_loop = ent:get_animation()
if (not data.animation_frames) or (anim_range.x ~= data.x) or (anim_range.y ~= data.y) then
data.runtime = 0
data.animations_frames = false
elseif data.animation_frames then
data.runtime = data.runtime + dt
data.current_frame = math.clamp(data.runtime*data.fps, data.animation_frames.x, data.animation_frames.y)
data.runtime = data.runtime + dt
data.current_frame = 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
end
--IMPORTANT!!! this does not directly modify the animation_data table, 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 loop? I DONT FUCKIN KNOW
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(length or fps, "need either length or FPS for animation")
assert(not (length and fps), "cannot play animation with both specified length and specified fps. Only one parameter can be used.")
local num_frames = math.abs(frames.x-frames.y)
local data = self.animation_data
if length then
fps = num_frames/length
elseif fps then
length = num_frames/fps
else
elseif not fps then
fps = self.consts.DEFAULT_FPS
length = num_frames/self.consts.DEFAULT_FPS
end
data.runtime = 0
data.length = length
self.entity:set_animation(frames, fps, 0, loop)
end
function gun_default:clear_animation()
@ -464,31 +532,34 @@ function gun_default:clear_animation()
elseif self.ammo_handler.ammo.total_bullets > 0 then
loaded = true
end
if loaded then
self.entity:set_animation({x=self.properties.animations.loaded.x, y=self.properties.animations.loaded.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
else
self.entity:set_animation({x=self.properties.animations.empty.x, y=self.properties.animations.empty.y}, 0, 0, self.consts.LOOP_IDLE_ANIM)
end
minetest.chat_send_all(tostring(loaded))
minetest.chat_send_all(self.ammo_handler.ammo.loaded_mag)
local data = self.animation_data
data.runtime = 0
data.length = false
data.animations_frames = false
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
minetest.chat_send_all(self.properties.visuals.animations.empty.x)
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
function gun_default:update_breathing(dt)
local breathing_info = {pause=1.4, rate=4.2}
--we want X to be between 0 and 4.2. Since math.pi is a positive crest, we want X to be above it before it reaches our-
--"length" (aka rate-pause), thus it will pi/length or pi/(rate-pause) will represent out slope of our control.
local x = (self.time_since_creation%breathing_info.rate)*math.pi/(breathing_info.rate-breathing_info.pause)
local scale = 1
--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 linearly.
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=self.offsets.breathing.player_axial-(self.offsets.breathing.player_axial*2*dt)
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 = scale*(math.sin(x))
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
@ -498,10 +569,12 @@ function gun_default:update_sway(dt)
end
end)
ran.z = 0
sway_vel = Vec.normalize(sway_vel+(ran*dt))*self.properties.sway.angular_velocity[axis]
local vel_mul = self.multiplier_coefficient(sprops.hipfire_velocity_multiplier[axis], 1-self.handler.ads_location)
sway_vel = Vec.normalize(sway_vel+(ran*dt))*sprops.angular_velocity[axis]*vel_mul
sway=sway+(sway_vel*dt)
if Vec.length(sway) > self.properties.sway.max_angle[axis] then
sway=Vec.normalize(sway)*self.properties.sway.max_angle[axis]
local len_mul = self.multiplier_coefficient(sprops.hipfire_angle_multiplier[axis], 1-self.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
@ -509,13 +582,39 @@ function gun_default:update_sway(dt)
end
end
--this needs to save results eventually. Shouldn't be particularly important though. <-not sure what the fuck I meant by that
--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
local frame1 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)
local frame2 = math.floor(current_frame/self.consts.KEYFRAME_SAMPLE_PRECISION)+1
for i, v in pairs(out) do
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)/self.consts.KEYFRAME_SAMPLE_PRECISION
local vec1 = self.b3d_model.global_frames[i][frame1]
local vec2 = self.b3d_model.global_frames[i][frame2]
out[i] = vec1+((vec1-vec2)*ip_ratio)
end
else
out[i]=vector.copy(self.b3d_model.global_frames[i][1])
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()
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
--construction for the base gun class
local valid_ctrls = {
local valid_ctrls = { --for validation of controls.
up=true,
down=true,
left=true,
@ -560,19 +659,14 @@ gun_default.construct = function(def)
--initialize all offsets
--def.offsets = table.deep_copy(def.base_class.offsets)
def.offsets = {}
for i, tbl in pairs(def.base_class.offsets) do
if (tbl.gun_axial and tbl.player_axial) then
local ty = type(tbl.gun_axial)
if (ty=="table") and tbl.gun_axial.x and tbl.gun_axial.y and tbl.gun_axial.z then
def.offsets[i] = {}
def.offsets[i].gun_axial = Vec.new()
def.offsets[i].player_axial = Vec.new()
for offset, tbl in pairs(def.base_class.offsets) do
def.offsets[offset] = {}
for i, v in pairs(tbl) do
if type(v) == "table" and v.x then
def.offsets[offset][i] = vector.new()
else
def.offsets[i] = {}
def.offsets[i] = table.deep_copy(def.offsets[i])
def.offsets[offset][i] = v
end
elseif tbl.x and tbl.y and tbl.z then
def.offsets[i] = Vec.new()
end
end
@ -580,18 +674,19 @@ gun_default.construct = function(def)
def.velocities = {}
for i, tbl in pairs(def.base_class.velocities) do
def.velocities[i] = {}
if tbl.gun_axial and tbl.player_axial then
def.velocities[i].gun_axial = Vec.new()
def.velocities[i].player_axial = Vec.new()
end
def.velocities[i].gun_axial = Vec.new()
def.velocities[i].player_axial = Vec.new()
end
--properties have been assigned, create necessary objects
--properties have been assigned, create necessary objects TODO: completely change this system for defining them.
if def.properties.sprite_scope then
if not def.sprite_scope then
def.sprite_scope = def.properties.sprite_scope:new({
gun = def
})
end
def.sprite_scope = def.properties.sprite_scope:new({
gun = def
})
end
if def.properties.crosshair then
def.crosshair = def.properties.crosshair:new({
gun = def
})
end
if def.properties.entity_scope then
if not def.entity_scope then
@ -624,8 +719,20 @@ gun_default.construct = function(def)
props = def.properties --have to reinitialize this as the reference is replaced.
--print(table.tostring(props))
def.model_handler = Guns4d.Model_bone_handler:new({modelpath = props.mesh})
def.b3d_model = mtul.b3d_reader.read_model(props.visuals.mesh, true)
def.b3d_model.global_frames = {
arm_right = {}, --the aim position of the right arm
arm_left = {}, --the aim position of the left arm
rotation = {} --rotation of the gun (this is assumed as gun_axial, but that's probably fucked for holo sight alignments)
}
--print(table.tostring(def.b3d_model))
--precalculate keyframe "samples" for intepolation.
--mildly infuriating that lua just stops short by one (so I have to add one extra) I *think* I get why though.
for target_frame = 0, def.b3d_model.node.animation.frames+1, def.consts.KEYFRAME_SAMPLE_PRECISION do
table.insert(def.b3d_model.global_frames.arm_right, vector.new(mtul.b3d_nodes.get_node_global_position(def.b3d_model, props.visuals.arm_right, true, target_frame))/10)
table.insert(def.b3d_model.global_frames.arm_left, vector.new(mtul.b3d_nodes.get_node_global_position(def.b3d_model, props.visuals.arm_left, true, target_frame))/10)
--def.b3d_model.rotation =
end
if def.name ~= "__template" then
assert(rawget(def, "name"), "no name provided in new class")
assert(rawget(def, "itemstring"), "no itemstring provided in new class")
@ -667,14 +774,13 @@ gun_default.construct = function(def)
minetest.register_entity(def.name.."_visual", {
initial_properties = {
visual = "mesh",
mesh = props.mesh,
mesh = props.visuals.mesh,
textures = props.textures,
glow = 0,
pointable = false,
static_save = false,
},
on_step = function(self, dtime)
local name = string.gsub(self.name, "_visual", "")
local obj = self.object
if not self.parent_player then obj:remove() return end
local player = self.parent_player
@ -684,10 +790,10 @@ gun_default.construct = function(def)
--this is changing the point of rotation if not aiming, this is to make it look less shit.
local axial_modifier = Vec.new()
if not handler.control_bools.ads then
local pitch = lua_object.offsets.total_offset_rotation.player_axial.x+lua_object.offsets.player_rotation.x
local pitch = lua_object.total_offset_rotation.player_axial.x+lua_object.player_rotation.x
axial_modifier = Vec.new(pitch*(1-lua_object.consts.HIP_PLAYER_GUN_ROT_RATIO),0,0)
end
local axial_rot = lua_object.offsets.total_offset_rotation.gun_axial+axial_modifier
local axial_rot = lua_object.total_offset_rotation.gun_axial+axial_modifier
--attach to the correct bone, and rotate
local visibility = true
if lua_object.sprite_scope and lua_object.sprite_scope.hide_gun and (not (handler.ads_location == 0)) then
@ -703,7 +809,6 @@ gun_default.construct = function(def)
end
end,
})
--I don't know why lua's syntax makes me make it anon, but uh, fuck you.
end
end
Guns4d.gun = Instantiatable_class:inherit(gun_default)

View File

@ -1,117 +0,0 @@
local path_seperator = "/@/"
Guns4d.Model_bone_handler = Instantiatable_class:inherit({
construct = function(def)
if def.instance then
assert(def.modelpath, "no path provided")
if mtul.media_paths[def.modelpath] then def.modelpath = mtul.media_paths[def.modelpath] end
local stream = io.open(def.modelpath, "rb")
def.b3d_table = mtul.b3d.read(stream)
stream:close()
stream = minetest.request_insecure_environment().io.open(minetest.get_modpath("guns4d").."/test.gltf", "wb")
modlib.b3d.write_gltf(def.b3d_table, stream)
stream:close()
def.paths = {}
def:process_and_reformat()
end
end
})
local model_bones = Guns4d.Model_bone_handler
--this creates a list of bone paths, and changes the index from an int to names.
local function retrieve_hierarchy(node, out)
if not out then out = {node} end
if node.parent then
table.insert(out, 1, node.parent)
retrieve_hierarchy(node.parent, out)
end
return out
end
function model_bones:solve_global_transform(node)
assert(self.instance, "attempt to call object method on a class")
local global_transform
local hierarcy = retrieve_hierarchy(node)
print("start")
for i, v in pairs(hierarcy) do
print(i, v.name)
end
print("end")
for i, v in pairs(hierarcy) do
local pos_vec = v.position
local rot_vec = v.rotation
local scl_vec = v.scale
if v.keys[2] then
pos_vec = v.keys[2].position
rot_vec = v.keys[2].rotation
scl_vec = v.keys[2].scale
end
--rot_vec = {rot_vec[2], rot_vec[3], rot_vec[4], rot_vec[1]}
pos_vec = {-pos_vec[1], pos_vec[2], pos_vec[3]}
local pos = modlib.matrix4.translation(pos_vec)
rot_vec = {-rot_vec[1], rot_vec[2], rot_vec[3], rot_vec[4]}
local rot = modlib.matrix4.rotation(modlib.quaternion.normalize(rot_vec))
local scl = modlib.matrix4.scale(scl_vec)
local local_transform = scl:compose(rot):compose(pos)
if global_transform then
global_transform=global_transform:multiply(local_transform)
else
global_transform=local_transform
end
end
local pos
if node.keys[2] then
pos = node.position
else
pos = node.keys[2].position
end
--pos = global_transform:apply({pos[1], pos[2], pos[3], 1})
--print(dump(global_transform))
--return vector.new(pos[1], pos[2], pos[3])
return vector.new(global_transform[1][4], global_transform[2][4], global_transform[3][4])
end
function model_bones:get_bone_global(bone_name)
assert(self.instance, "attempt to call object method on a class")
for i, v in pairs(self.paths) do
local s, e = string.find(i, bone_name, #i-#bone_name)
--this needs to be fixed.
if s then
local v1, v2 = self:solve_global_transform(v)
return v1, v2
end
end
end
function model_bones:process_and_reformat(node, path)
assert(self.instance, "attempt to call object method on a class")
local first = false
if not node then
first = true
node = self.b3d_table.node
end
path = path or ""
node.mesh = nil --we wont be needing this
for i, v in pairs(node.children) do
if type(i) == "number" then
local newpath
if path ~= "" then
newpath = path.." @ "..v.name
else
newpath = v.name
end
self.paths[newpath] = v
v.mesh = nil
v.parent = node
node.children[v.name] = v
node.children[i] = nil
self:process_and_reformat(v, newpath)
end
end
if first then
for i, v in pairs(self.paths) do
print(i)
print(table.tostring(v.rotation))
print(table.tostring(v.position))
print(table.tostring(v.scale))
end
end
end

View File

@ -8,7 +8,7 @@ local player_handler = {
--wielded_item = ItemStack
--gun = Gun (class)
--wield_index = Int
--model_handler = player_model_handler
--player_model_handler = player_model_handler
look_rotation = {x=0, y=0},
look_offset = Vec.new(),
ads_location = 0, --interpolation scalar for gun aiming location
@ -16,7 +16,6 @@ local player_handler = {
fov = 80,
horizontal_offset = 0
}
local model_handler = Guns4d.player_model_handler
function player_handler:update(dt)
assert(self.instance, "attempt to call object method on a class")
local player = self.player
@ -39,11 +38,11 @@ function player_handler:update(dt)
end
self.gun = held_gun:new({itemstack=self.wielded_item, handler=self}) --this will set itemstack meta, and create the gun based off of meta and other data.
----model handler----
if self.model_handler then --if model_handler present, then delete
self.model_handler:prepare_deletion()
self.model_handler = nil
if self.player_model_handler then --if player_model_handler present, then delete
self.player_model_handler:prepare_deletion()
self.player_model_handler = nil
end
self.model_handler = model_handler.get_handler(self:get_properties().mesh):new({player=self.player})
self.player_model_handler = Guns4d.player_model_handler.get_handler(self:get_properties().mesh):new({player=self.player})
----control handler----
self.control_handler = Guns4d.control_handler:new({player=player, controls=self.gun.properties.controls})
@ -65,7 +64,7 @@ function player_handler:update(dt)
--update handlers
self.gun:update(dt) --gun should be updated first so self.dir is available.
self.control_handler:update(dt)
self.model_handler:update(dt)
self.player_model_handler:update(dt)
--this has to be checked after control handler
if TICK % 4 == 0 then
@ -79,8 +78,8 @@ function player_handler:update(dt)
self.gun = nil
self:reset_controls_table() --return controls to default
--delete model handler object (this resets the player model)
self.model_handler:prepare_deletion()
self.model_handler = nil
self.player_model_handler:prepare_deletion()
self.player_model_handler = nil
player:hud_set_flags({wielditem = true, crosshair = true}) --reenable hud elements
end

View File

@ -7,51 +7,107 @@ local Vec = vector
arm_left_global = vector.new(3.15, 11.55, 0),
}]]
Guns4d.player_model_handler = {
handlers = {}, --not for children, this stores a global list of handlers by meshname.
offsets = {
arm = {
right = Vec.new(-3.15, 11.55, 0),
rltv_right = Vec.new(-3.15, 5.5, 0),
left = Vec.new(3.15, 11.55, 0),
rltv_left = Vec.new(3.15, 5.5, 0)
global = {
--right arm (for hipfire bone)
},
relative = { --none of these are specifically needed... perhaps delegate this to the
--left arm
--right arm
--head
},
head = Vec.new(0,6.3,0)
},
handlers = {},
mesh = "guns3d_character.b3d"
inv_rotation = {}, --stores inverse rotation for bone aiming
--REMEMBER! bones must be named differently from their original model's counterparts, because minetest was written by monkeys who were supervised by clowns. (no way to unset them.)
bone_names = {
arm_right = "guns3d_arm_right",
arm_left = "guns3d_arm_left",
aim = "guns3d_aiming_bone",
hipfire = "guns3d_hipfire_bone",
head = "guns3d_head"
},
still_frame = 0, --the frame to take bone offsets from. This system has to be improved in the future (to allow better animation support)- it works for now though.
compatible_meshes = { --list of meshes and their corresponding partner meshes for this handler.
["character.b3d"] = "guns3d_character.b3d"
},
fallback_mesh = "guns3d_character.b3d", --if no meshes are found in "compatible_meshes" it chooses this one.
is_new_default = true --this will set the this to be the default handler.
}
local player_model = Guns4d.player_model_handler
function player_model:set_default_handler()
assert(not self.instance, "cannot set default handler to an instance of a handler")
player_model.default_handler = self
function player_model.set_default_handler(class_or_name)
assert(class_or_name, "class or mesh name (string) needed. Example: 'character.b3d' sets the default handler to whatever handler is used for character.b3d.")
local handler = assert(((type(class_or_name) == "table") and class_or_name) or player_model.get_handler(class_or_name), "no handler by the name '"..tostring(class_or_name).."' found.")
assert(not handler.instance, "cannot set instance of a handler as the default player_model_handler")
player_model.default_handler = handler
end
function player_model:get_handler(meshname)
local selected_handler = player_model.handlers[meshname]
function player_model.get_handler(meshname)
local selected_handler = player_model.handlers[meshname] or player_model.main
if selected_handler then return selected_handler end
return player_model.default_handler
end
function player_model:update()
function player_model:add_compatible_mesh(original, replacement)
assert(not self.instance, "attempt to call class method on an object. Cannot modify original class from an instance.")
assert(original and replacement, "one or more parameters missing")
self.compatible_meshes[original] = replacement
player_model.handlers[original] = self
end
function player_model:update(dt)
assert(dt, "delta time (dt) not provided.")
assert(self.instance, "attempt to call object method on a class")
local player = self.player
local handler = Guns4d.players[player:get_player_name()].handler
local handler = self.handler
local gun = handler.gun
local player_axial_offset = gun.offsets.total_offset_rotation.player_axial
local pitch = player_axial_offset.x+gun.offsets.player_rotation.x
local combined = player_axial_offset+gun.offsets.total_offset_rotation.gun_axial+Vec.new(gun.offsets.player_rotation.x,0,0)
local player_axial_offset = gun.total_offset_rotation.player_axial
local pitch = player_axial_offset.x+gun.player_rotation.x
--gun bones:
local first, second = player:get_eye_offset()
local eye_pos = vector.new(0, handler:get_properties().eye_height*10, 0)+first
if handler.control_bools.ads then
eye_pos.x = handler.horizontal_offset*10
end
player:set_bone_position("guns3d_hipfire_bone", self.offsets.arm.rltv_right, vector.new(-(pitch*gun.consts.HIP_PLAYER_GUN_ROT_RATIO), 180-player_axial_offset.y, 0))
player:set_bone_position("guns3d_reticle_bone", eye_pos, vector.new(combined.x, 180-combined.y, 0))
player:set_bone_position("guns3d_head", self.offsets.head, {x=pitch,z=0,y=0})
player:set_bone_position(self.bone_names.hipfire, self.offsets.relative.arm_right, {x=-(pitch*gun.consts.HIP_PLAYER_GUN_ROT_RATIO), y=180-player_axial_offset.y, z=0})
--player:set_bone_position(self.bone_names.reticle, eye_pos, vector.new(combined.x, 180-combined.y, 0))
--can't use paxial dir as it needs to be relative on Y still.
local dir = vector.rotate(gun.local_paxial_dir, {x=gun.offsets.player_rotation.x*math.pi/180,y=0,z=0})
local dir = vector.rotate(gun.local_paxial_dir, {x=gun.player_rotation.x*math.pi/180,y=0,z=0})
local rot = vector.dir_to_rotation(dir)*180/math.pi
player:set_bone_position("guns3d_aiming_bone", eye_pos, {x=rot.x,y=-rot.y+180,z=0})
--irrlicht uses clockwise rotations, while everything else seemingly uses counter-clockwise. MMM yes, it's an "engine" not sphaghetti
player:set_bone_position(self.bone_names.aim, eye_pos, {x=rot.x,y=180-rot.y,z=0})
self:update_head(dt)
self:update_arm_bones(dt)
end
--this is seperate as different models may use different coordinate systems for this. I tried to make it automatic, but irrlicht is a load of trash.
function player_model:update_arm_bones(dt)
local player = self.player
local handler = self.handler
local gun = handler.gun
local left_bone, right_bone = self.offsets.global.arm_left, self.offsets.global.arm_right
local left_trgt, right_trgt = gun:get_arm_aim_pos() --this gives us our offsets relative to the gun.
--get the real position of the gun's bones relative to the player (2nd param true)
left_trgt = gun:get_pos(left_trgt, true)
right_trgt = gun:get_pos(right_trgt, true)
local left_rotation = vector.dir_to_rotation(vector.direction(left_bone, left_trgt))*180/math.pi
local right_rotation = vector.dir_to_rotation(vector.direction(right_bone, right_trgt))*180/math.pi
--all of this is pure insanity. There's no logic, or rhyme or reason. Trial and error is the only way to write this garbo.
left_rotation.x = -left_rotation.x
right_rotation.x = -right_rotation.x
player:set_bone_position(self.bone_names.arm_left, self.offsets.relative.arm_left, {x=90, y=0, z=0}-left_rotation)
player:set_bone_position(self.bone_names.arm_right, self.offsets.relative.arm_right, {x=90, y=0, z=0}-right_rotation)
end
function player_model:update_head(dt)
local player = self.player
local handler = self.handler
local gun = handler.gun
local player_axial_offset = gun.total_offset_rotation.player_axial
local pitch = player_axial_offset.x+gun.player_rotation.x
player:set_bone_position(self.bone_names.head, self.offsets.relative.head, {x=pitch,z=0,y=0})
end
--should be renamed to "release" but, whatever.
function player_model:prepare_deletion()
assert(self.instance, "attempt to call object method on a class")
local handler = Guns4d.players[self.player:get_player_name()].handler
@ -62,23 +118,47 @@ function player_model:prepare_deletion()
properties.mesh = self.old
handler:set_properties(properties)
end
--todo: add value for "still_frame" (the frame to take samples from in case 0, 0 is not still.)
---@diagnostic disable-next-line: duplicate-set-field
function player_model.construct(def)
if def.instance then
assert(def.mesh, "model has no mesh")
assert(def.player, "no player provided")
local handler = Guns4d.players[def.player:get_player_name()].handler
local properties = handler:get_properties()
def.handler = Guns4d.players[def.player:get_player_name()].handler
local properties = def.handler:get_properties()
def.old = properties.mesh
--set the model
if minetest.get_modpath("player_api") then
player_api.set_model(def.player, def.mesh)
end
properties.mesh = def.mesh
handler:set_properties(properties)
--set the mesh
properties.mesh = def.compatible_meshes[properties.mesh] or def.fallback_mesh
def.handler:set_properties(properties)
else
if def.replace then
player_model.handlers[def.replace] = def
for og_mesh, replacement_mesh in pairs(def.compatible_meshes) do
assert(type(og_mesh)=="string", "mesh to be replaced (index) must be a string!")
if player_model.handlers[og_mesh] then minetest.log("warning", "Guns4d: mesh '"..og_mesh.."' overridden by a handler class, this will replace the old handler. Is this a mistake?") end
player_model.handlers[replacement_mesh] = def
end
if def.is_new_default then
player_model.set_default_handler(def)
end
local i, v = next(def.compatible_meshes)
local b3d_table = mtul.b3d_reader.read_model(v, true)
--[[all of the compatible_meshes should be identical in terms of guns4d specific bones and offsets (arms, head).
Otherwise a new handler should be different. With new compatibilities]]
---@diagnostic disable-next-line: redefined-local
for i, v in pairs({"arm_right", "arm_left", "head"}) do
--print(def.bone_names[v])
local node = mtul.b3d_nodes.get_node_by_name(b3d_table, def.bone_names[v], true)
local transform, rotation = mtul.b3d_nodes.get_node_global_transform(node, def.still_frame)
def.offsets.relative[v] = vector.new(node.position[1], node.position[2], node.position[3])
def.offsets.global[v] = vector.new(transform[13], transform[14], transform[15])/10 --4th column first 3 rows give us our global transform.
--print(i, mtul.b3d_nodes.get_node_rotation(b3d_table, node, true, def.still_frame))
def.inv_rotation[v] = rotation:conjugate() --(note this overrides original matrix made in get_node_global_transform)
end
def.offsets.global.hipfire = vector.new(mtul.b3d_nodes.get_node_global_position(b3d_table, def.bone_names.arm_right, true, def.still_frame))
if def.is_new_default then
player_model.set_default_handler(def)
end
end
end

View File

@ -1,4 +1,4 @@
Sprite_scope = Instantiatable_class:inherit({
local Sprite_scope = Instantiatable_class:inherit({
images = {
fore = {
texture = "blank.png",
@ -52,6 +52,8 @@ Sprite_scope = Instantiatable_class:inherit({
end
end
})
Guns4d.sprite_scope = Sprite_scope
--rename to draw?
function Sprite_scope:update()
local handler = self.handler
if handler.wininfo and self.handler.control_bools.ads then
@ -69,7 +71,7 @@ function Sprite_scope:update()
self.player:hud_change(self.elements.reticle, "position", {x=(v1.x*self.images.reticle.movement_multiplier)+.5, y=(v1.y*self.images.reticle.movement_multiplier)+.5})
--update textures
end
local angle =math.sqrt(self.gun.offsets.total_offset_rotation.gun_axial.x^2+self.gun.offsets.total_offset_rotation.gun_axial.y^2)
local angle =math.sqrt(self.gun.total_offset_rotation.gun_axial.x^2+self.gun.total_offset_rotation.gun_axial.y^2)
for i, v in pairs(self.elements) do
local def = self.images[i]
local tex = def.texture

View File

@ -151,9 +151,11 @@ Guns4d.default_controls.reload = {
data.held = false
local anim = next_state.anim
if type(next_state.anim) == "string" then
anim = props.animations[next_state.anim]
anim = props.visuals.animations[next_state.anim]
end
if anim then
gun:set_animation(anim, next_state.time)
end
gun:set_animation(anim, next_state.time)
end
elseif interrupted then
local this_state = props.reload[data.state]

View File

@ -11,18 +11,16 @@ dofile(path.."/block_values.lua")
dofile(path.."/register_ammo.lua")
path = path .. "/classes"
dofile(path.."/Instantiatable_class.lua")
dofile(path.."/Model_reader.lua")
dofile(path.."/Bullet_ray.lua")
dofile(path.."/Control_handler.lua")
dofile(path.."/Ammo_handler.lua")
dofile(path.."/Sprite_scope.lua")
dofile(path.."/Dynamic_crosshair.lua")
dofile(path.."/Gun.lua")
dofile(path.."/Player_model_handler.lua")
dofile(path.."/Player_handler.lua")
dofile(path.."/Proxy_table.lua")
Guns4d.Model_bone_handler:new({modelpath="model_reader_test.b3d"})
--load after
path = minetest.get_modpath("guns4d")
@ -34,10 +32,10 @@ minetest.register_on_joinplayer(function(player)
handler = player_handler:new({player=player})
}
player:set_fov(80)
--ObjRef overrides will be integrated into MTUL (eventually TM)
if not objref_mtable then
objref_mtable = getmetatable(player)
--putting this here is hacky as fuck.
local old_get_pos = objref_mtable.get_pos
function objref_mtable.get_pos(self)
local gun = Guns4d.gun_by_ObjRef[self]
@ -48,6 +46,40 @@ minetest.register_on_joinplayer(function(player)
return v
end
end
local old_set_animation = objref_mtable.set_animation
--put vargs there for maintainability.
function objref_mtable.set_animation(self, frame_range, frame_speed, frame_blend, frame_loop, ...)
local gun = Guns4d.gun_by_ObjRef[self]
if gun then
local data = gun.animation_data
data.runtime = 0
data.fps = frame_speed or 15
data.loop = frame_loop --still have no idea what nutjob made the default true >:(
if frame_loop == nil then
frame_loop = true
end
--so... minetest is stupid, and so it won't let me set something to the same animation twice (utterly fucking brilliant).
--This means I literally need to flip flop between +1 frames
frame_range = (frame_range and table.copy(frame_range)) or {x=1,y=1}
if data.frames.x == frame_range.x and data.frames.y == frame_range.y then
frame_range.y = frame_range.y + 1 --oh yeah, and it only accepts whole frames... because of course.
end
data.frames = frame_range
data.current_frame = data.frames.x
end
return old_set_animation(self, frame_range, frame_speed, frame_blend, frame_loop, ...)
end
local old_set_frame_speed = objref_mtable.set_animation_frame_speed
function objref_mtable.set_animation_frame_speed(self, frame_speed, ...)
local gun = Guns4d.gun_by_ObjRef[self]
if gun then
gun.animation_data.fps = frame_speed or 15
end
old_set_frame_speed(self, frame_speed, ...)
end
local old_remove = objref_mtable.remove
function objref_mtable.remove(self)
local gun = Guns4d.gun_by_ObjRef[self]

View File

@ -120,7 +120,7 @@ local function parse_index(i)
end
end
--dump() sucks.
function table.tostring(tbl, shallow, tables, depth)
function table.tostring(tbl, shallow, long_lists, tables, depth)
--create a list of tables that have been tostringed in this chain
if not table then return "nil" end
if not tables then tables = {this_table = tbl} end
@ -131,7 +131,12 @@ function table.tostring(tbl, shallow, tables, depth)
for i = 1, depth do
initial_string = initial_string .. " "
end
if depth > 20 then
return "(TABLE): depth limited reached"
end
local iterations = 0
for i, v in pairs(tbl) do
iterations = iterations + 1
local val_type = type(v)
if val_type == "string" then
str = str..initial_string..parse_index(i).." = \""..v.."\","
@ -140,17 +145,63 @@ function table.tostring(tbl, shallow, tables, depth)
--to avoid infinite loops, make sure that the table has not been tostringed yet
if not contains then
tables[i] = v
str = str..initial_string..parse_index(i).." = "..table.tostring(v, shallow, tables, depth)..","
str = str..initial_string..parse_index(i).." = "..table.tostring(v, shallow, long_lists, tables, depth)..","
else
str = str..initial_string..parse_index(i).." = "..tostring(v).." ("..contains.."),"
str = str..initial_string..parse_index(i).." = "..tostring(v).." (index: '"..tostring(contains).."'),"
end
else
str = str..initial_string..parse_index(i).." = "..tostring(v)..","
end
end
if iterations > 100 and not long_lists then
return "(TABLE): too long, 100+ indices"
end
return str..string.sub(initial_string, 1, -5).."}"
end
function table.tostring_structure_only(tbl, shallow, tables, depth)
--create a list of tables that have been tostringed in this chain
if not table then return "nil" end
if not tables then tables = {this_table = tbl} end
if not depth then depth = 0 end
depth = depth + 1
local str = ""
local initial_string = "\n"
for i = 1, depth do
initial_string = initial_string .. " "
end
if depth > 20 then
return "(TABLE): depth limited reached (20 nested tables)"
end
local iterations = 0
if tbl.name then
str = str..initial_string.."[\"name\"] = \""..tbl.name.."\","
end
if tbl.type then
str = str..initial_string.."[\"type\"] = \""..tbl.type.."\","
end
for i, v in pairs(tbl) do
iterations = iterations + 1
local val_type = type(v)
if val_type == "table" then
local contains = table.contains(tables, v)
--to avoid infinite loops, make sure that the table has not been tostringed yet
if not contains then
tables[parse_index(i).." ["..tostring(v).."]"] = v
str = str..initial_string..parse_index(i).."("..tostring(v)..") = "..table.tostring_structure_only(v, shallow, tables, depth)..","
elseif type(v) == "table" then
str = str..initial_string..parse_index(i).." = "..tostring(v)
else
str = str..initial_string..parse_index(i).." = "..tostring(v).." ("..tostring(v).."),"
end
end
end
if iterations == 0 then
return "{}"
elseif iterations > 100 then
return "table too long"
end
return "{"..str..string.sub(initial_string, 1, -5).."}"
end
--replace fields (and fill sub-tables) in `tbl` with elements in `replacement`. Recursively iterates all sub-tables. use property __overfill=true for subtables that don't want to be overfilled.
function table.fill(tbl, replacement, preserve_reference, indexed_tables)
@ -210,9 +261,6 @@ function table.shallow_copy(t)
return new_table
end
function weighted_randoms()
end
--for the following code and functions only:
--for license see the link on the next line.
--https://github.com/3dreamengine/3DreamEngine
@ -234,5 +282,5 @@ function Point_to_hud(pos, fov, aspect)
local x = (pos.x/pos.z)*a1
local y = (pos.y/pos.z)*a6
local z = (pos.z/pos.z)*a11
return vector.new(x / 2, -y / 2, z)
return {x=x / 2,y=-y / 2,}
end

Binary file not shown.

View File

@ -59,6 +59,7 @@ function Guns4d.ammo.register_magazine(def)
assert(def.itemstring, "missing item name")
def.accepted_bullets_set = {} --this table is a "lookup" table, I didn't go to college so I have no idea
for i, v in pairs(def.accepted_bullets) do
--TODO: make an actual error/minetest.log
if not Guns4d.ammo.registered_bullets[v] then print("guns4D: WARNING! bullet "..v.." not registered! is this a mistake?") end
def.accepted_bullets_set[v] = true
end

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -10,8 +10,8 @@ function Guns4d.effects.muzzle_flash(self)
end
local dir, offset_pos = self.dir, self:get_pos(self.properties.flash_offset)
offset_pos=offset_pos+self.player:get_pos()
local min = vector.rotate(vector.new(-1, -1, -.15), {x=0,y=self.offsets.player_rotation.y,z=0})
local max = vector.rotate(vector.new(1, 1, .15), {x=0,y=self.offsets.player_rotation.y,z=0})
local min = vector.rotate(vector.new(-1, -1, -.15), {x=0,y=self.player_rotation.y,z=0})
local max = vector.rotate(vector.new(1, 1, .15), {x=0,y=self.player_rotation.y,z=0})
minetest.add_particlespawner({
exptime = .18,
time = .1,