first commit
This commit is contained in:
commit
5bd53dc6a2
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Lua.diagnostics.globals": [
|
||||
"minetest",
|
||||
"vector",
|
||||
"dump",
|
||||
"player_api",
|
||||
"ItemStack"
|
||||
]
|
||||
}
|
75
block_values.lua
Normal file
75
block_values.lua
Normal file
@ -0,0 +1,75 @@
|
||||
Guns4d.node_properties = {}
|
||||
--{["default:gravel"] = {rha=2, random_deviation=1, behavior="normal"}, . . . }
|
||||
--behavior types:
|
||||
--normal, bullets hit and penetrate
|
||||
--breaks, bullets break it but still applies RHA/randomness values (etc)
|
||||
--ignore, bullets pass through
|
||||
|
||||
--unimplemented
|
||||
|
||||
--liquid, bullets hit and penetrate, but effects are different
|
||||
--damage, bullets hit and penetrate, but replace with "replace = _"
|
||||
|
||||
--mmRHA of wood .05 (mostly arbitrary)
|
||||
--{choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, wood = 1}
|
||||
|
||||
--this is really the best way I could think of to do this
|
||||
--in a perfect world you could perfectly balance each node, but a aproximation will have to do
|
||||
--luckily its still an option, if you are literally out of your fucking mind.
|
||||
minetest.register_on_mods_loaded(function()
|
||||
for i, v in pairs(minetest.registered_nodes) do
|
||||
local groups = v.groups
|
||||
local RHA = 1
|
||||
local random_deviation = 1
|
||||
local behavior_type = "normal"
|
||||
if groups.wood then
|
||||
RHA = RHA*groups.wood*.1
|
||||
random_deviation = random_deviation/groups.wood
|
||||
end
|
||||
if groups.oddly_breakable_by_hand then
|
||||
RHA = RHA / groups.oddly_breakable_by_hand
|
||||
end
|
||||
if groups.choppy then
|
||||
RHA = RHA*(1+(groups.choppy*.5))
|
||||
end
|
||||
if groups.flora or groups.grass then
|
||||
RHA = 0
|
||||
random_deviation = 0
|
||||
behavior_type = "ignore"
|
||||
end
|
||||
if groups.leaves then
|
||||
RHA = .0001
|
||||
random_deviation = .005
|
||||
end
|
||||
if groups.stone then
|
||||
RHA = groups.stone
|
||||
random_deviation = .5
|
||||
end
|
||||
if groups.cracky then
|
||||
RHA = RHA*groups.cracky
|
||||
random_deviation = random_deviation*(groups.cracky*.5)
|
||||
end
|
||||
if groups.crumbly then
|
||||
RHA = RHA/groups.crumbly
|
||||
end
|
||||
if groups.soil then
|
||||
RHA = RHA*(groups.soil*2)
|
||||
end
|
||||
if groups.sand then
|
||||
RHA = RHA*(groups.sand*2)
|
||||
end
|
||||
if groups.liquid then
|
||||
--behavior type here
|
||||
RHA = .5
|
||||
random_deviation = .1
|
||||
end
|
||||
Guns4d.node_properties[i] = {mmRHA=RHA*1000, random_deviation=random_deviation, behavior=behavior_type}
|
||||
end
|
||||
end)
|
||||
function Guns4d.override_node_propertoes(node, table)
|
||||
--TODO: check if node is valid
|
||||
assert(type(table.mmRHA)=="number", "no mmRHA value provided in override")
|
||||
assert(type(table.behavior)=="string", "no behavior type provided in override")
|
||||
assert(type(table.behavior)=="number", "no random_deviation value provided in override")
|
||||
Guns4d.node_properties[node] = table
|
||||
end
|
184
classes/Bullet_ray.lua
Normal file
184
classes/Bullet_ray.lua
Normal file
@ -0,0 +1,184 @@
|
||||
local ray = {
|
||||
history = {},
|
||||
state = "free",
|
||||
--pos = pos,
|
||||
last_node = "",
|
||||
normal = vector.new(),
|
||||
--last_dir
|
||||
--exit_direction = dir,
|
||||
--range_left = def.bullet.range,
|
||||
--force_mmRHA = def.bullet.penetration_RHA
|
||||
ITERATION_DISTANCE = .3,
|
||||
damage = 0
|
||||
}
|
||||
function ray:validate_location()
|
||||
end
|
||||
|
||||
function ray:record_state()
|
||||
table.insert(self.history, {
|
||||
state = self.state
|
||||
|
||||
})
|
||||
end
|
||||
--find (valid) edge. Slabs or other nodeboxes that are not the last hit position are not considered (to account for holes) TODO: update to account for hollow nodes
|
||||
function ray:transverse_end_point()
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local pointed
|
||||
local cast = minetest.raycast(self.pos+(self.dir*(self.ITERATION_DISTANCE+.01)), self.pos, false, false)
|
||||
for hit in cast do
|
||||
--we can't solidly predict all nodes, so ignore them as the distance will be solved regardless. If node name is different then
|
||||
if hit.type == "node" and (vector.equals(hit.under, self.last_pointed.under) or not minetest.registered_nodes[self.last_node_name].node_box) then
|
||||
pointed = hit
|
||||
break
|
||||
end
|
||||
end
|
||||
if pointed and vector.distance(pointed.intersection_point, self.pos) < self.ITERATION_DISTANCE then
|
||||
self.normal = pointed.intersection_normal
|
||||
self.exit_direction = vector.direction(self.dir, vector.new()) --reverse dir is exit direction (for VFX)
|
||||
return pointed.intersection_point
|
||||
end
|
||||
end
|
||||
function ray:cast()
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local end_pos = self.pos+(self.dir*self.range)
|
||||
--if block ends early, then we set end position accordingly
|
||||
local next_penetration_val
|
||||
local edge
|
||||
local next_state = self.state
|
||||
if self.state == "transverse" then
|
||||
edge = self:transverse_end_point()
|
||||
if edge then
|
||||
end_pos = edge
|
||||
next_state = "free"
|
||||
else
|
||||
end_pos = self.pos+(self.dir*self.ITERATION_DISTANCE)
|
||||
end
|
||||
end
|
||||
local continue = true
|
||||
local cast = minetest.raycast(self.pos, end_pos, true, true)
|
||||
local pointed
|
||||
for hit in cast do
|
||||
if not continue then break end
|
||||
if vector.distance(hit.intersection_point, self.pos) > 0.0005 and vector.distance(hit.intersection_point, self.pos) < self.range then
|
||||
--if it's a node, check that it's note supposed to be ignored according to it's generated properties
|
||||
if hit.type == "node" then
|
||||
if self.state == "free" and Guns4d.node_properties[minetest.get_node(hit.under).name].behavior ~= "ignore" then
|
||||
next_state = "transverse"
|
||||
pointed = hit
|
||||
break
|
||||
end
|
||||
if self.state == "transverse" then
|
||||
--if it isn't the same name as the last node we intersected, then it's a different block with different stats for penetration
|
||||
if minetest.get_node(hit.under).name ~= self.last_node_name then
|
||||
pointed = hit
|
||||
end
|
||||
--make sure it's set to transverse if the edge has a block infront of it
|
||||
if Guns4d.node_properties[minetest.get_node(hit.under).name].behavior == "ignore" then
|
||||
next_state = "free"
|
||||
else
|
||||
next_state = "transverse"
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
--if it's an object, make sure it's not the player object
|
||||
--note that while it may seem like this will create a infinite hit loop, it resolves itself as the intersection_point of the next ray will be close enough as to skip the pointed. See first line of iterator.
|
||||
if hit.type == "object" and hit.ref ~= self.player then
|
||||
if self.over_penetrate then
|
||||
pointed = hit
|
||||
break
|
||||
else
|
||||
pointed = hit
|
||||
continue = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if pointed then
|
||||
end_pos = pointed.intersection_point
|
||||
if self.state == "transverse" then
|
||||
next_penetration_val = self.force_mmRHA-(vector.distance(self.pos, end_pos)*Guns4d.node_properties[self.last_node_name].mmRHA)
|
||||
else -- transverse
|
||||
next_penetration_val = self.force_mmRHA-(vector.distance(self.pos, end_pos)*self.dropoff_mmRHA)
|
||||
end
|
||||
else
|
||||
--if there is no pointed, and it's not transverse, then the ray has ended.
|
||||
if self.state == "transverse" then
|
||||
next_penetration_val = self.force_mmRHA-(vector.distance(self.pos, end_pos)*Guns4d.node_properties[self.last_node_name].mmRHA)
|
||||
else --free
|
||||
continue = false
|
||||
next_penetration_val = self.force_mmRHA-(self.range*self.dropoff_mmRHA)
|
||||
end
|
||||
end
|
||||
|
||||
--set "last" values.
|
||||
return pointed, next_penetration_val, next_state, end_pos, continue
|
||||
end
|
||||
function ray:apply_damage(obj)
|
||||
local damage = math.floor((self.damage*(self.force_mmRHA/self.init_force_mmRHA))+1)
|
||||
obj:punch(self.player, nil, {damage_groups = {fleshy = damage, penetration_mmRHA=self.force_mmRHA}}, self.dir)
|
||||
end
|
||||
function ray:iterate(initialized)
|
||||
assert(self.instance, "attempt to call obj method on a class")
|
||||
local pointed, penetration, next_state, end_pos, continue = self:cast()
|
||||
self.range = self.range-vector.distance(self.pos, end_pos)
|
||||
self.pos = end_pos
|
||||
self.force_mmRHA = penetration
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
self.state = next_state
|
||||
if pointed then
|
||||
self.last_pointed = pointed
|
||||
end
|
||||
if pointed then
|
||||
if pointed.type == "node" then
|
||||
self.last_node_name = minetest.get_node(pointed.under).name
|
||||
elseif pointed.type == "object" then
|
||||
ray:apply_damage(pointed.ref)
|
||||
end
|
||||
end
|
||||
table.insert(self.history, {
|
||||
pos = self.pos,
|
||||
force_mmRHA = self.force_mmRHA,
|
||||
state = self.state,
|
||||
last_node = self.last_node_name,
|
||||
normal = self.normal,
|
||||
})
|
||||
if continue and self.range > 0 and self.force_mmRHA > 0 then
|
||||
self:iterate(true)
|
||||
end
|
||||
if not initialized then
|
||||
for i, v in pairs(self.history) do
|
||||
--[[local hud = self.player:hud_add({
|
||||
hud_elem_type = "waypoint",
|
||||
text = "mmRHA:"..tostring(math.floor(v.force_mmRHA or 0)).." ",
|
||||
number = 255255255,
|
||||
precision = 1,
|
||||
world_pos = v.pos,
|
||||
scale = {x=1, y=1},
|
||||
alignment = {x=0,y=0},
|
||||
offset = {x=0,y=0},
|
||||
})
|
||||
minetest.after(40, function(hud)
|
||||
self.player:hud_remove(hud)
|
||||
end, hud)]]
|
||||
end
|
||||
end
|
||||
end
|
||||
function ray.construct(def)
|
||||
if def.instance then
|
||||
assert(def.player, "no player")
|
||||
assert(def.pos, "no position")
|
||||
assert(def.dir, "no direction")
|
||||
assert(def.gun, "no Gun object")
|
||||
assert(def.range, "no range")
|
||||
assert(def.force_mmRHA, "no force")
|
||||
assert(def.dropoff_mmRHA, "no force dropoff")
|
||||
def.init_force_mmRHA = def.force_mmRHA
|
||||
def.dir = vector.new(def.dir)
|
||||
def.pos = vector.new(def.pos)
|
||||
def.history = {}
|
||||
def:iterate()
|
||||
end
|
||||
end
|
||||
Guns4d.bullet_ray = Instantiatable_class:inherit(ray)
|
82
classes/Control_handler.lua
Normal file
82
classes/Control_handler.lua
Normal file
@ -0,0 +1,82 @@
|
||||
Guns4d.control_handler = {
|
||||
--[[example:
|
||||
controls = {
|
||||
reload = {
|
||||
conditions = { --the list of controls (see lua_api.txt) to call
|
||||
"shift",
|
||||
"zoom"
|
||||
},
|
||||
timer = .3,
|
||||
call_before_timer = false,
|
||||
loop = false,
|
||||
func=function(active, interrupted, data, busy_controls)
|
||||
data = {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
]]
|
||||
}
|
||||
local controls = Guns4d.control_handler
|
||||
--[[-modify controls (future implementation if needed)
|
||||
function controls.modify()
|
||||
end]]
|
||||
function controls:update(dt)
|
||||
self.player_pressed = self.player:get_player_control()
|
||||
local pressed = self.player_pressed
|
||||
local call_queue = {} --so I need to have a "call" queue so I can tell the functions the names of other active controls (busy_list)
|
||||
local busy_list = {} --list of controls that have their conditions met
|
||||
for i, control in pairs(self.controls) do
|
||||
local def = control
|
||||
local data = control.data
|
||||
local conditions_met = true
|
||||
for _, key in pairs(control.conditions) do
|
||||
if not pressed[key] then conditions_met = false break end
|
||||
end
|
||||
if not conditions_met then
|
||||
busy_list[i] = true
|
||||
data.held = false
|
||||
--detect interrupts
|
||||
if data.timer ~= def.timer then
|
||||
table.insert(call_queue, {control=def, active=false, interrupt=true, data=data})
|
||||
data.timer = def.timer
|
||||
end
|
||||
else
|
||||
data.timer = data.timer - dt
|
||||
--when time is over, if it wasnt held (or loop is active) then reset and call the function.
|
||||
if data.timer <= 0 and ((not data.held) or def.loop) then
|
||||
data.held = true
|
||||
table.insert(call_queue, {control=def, active=true, interrupt=false, data=data})
|
||||
elseif def.call_before_timer then
|
||||
table.insert(call_queue, {control=def, active=false, interrupt=false, data=data})
|
||||
end
|
||||
end
|
||||
end
|
||||
local count = 0
|
||||
for i, v in pairs(busy_list) do
|
||||
count = count + 1
|
||||
end
|
||||
if count == 0 then busy_list = nil end --so funcs can quickly deduce if they can call
|
||||
for i, tbl in pairs(call_queue) do
|
||||
tbl.control.func(tbl.active, tbl.interrupt, tbl.data, busy_list, Guns4d.players[self.player:get_player_name()].handler)
|
||||
end
|
||||
end
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function controls.construct(def)
|
||||
if def.instance then
|
||||
assert(def.controls, "no controls provided")
|
||||
assert(def.player, "no player provided")
|
||||
def.controls = table.deep_copy(def.controls)
|
||||
for i, control in pairs(def.controls) do
|
||||
control.timer = control.timer or 0
|
||||
control.data = {
|
||||
timer = control.timer,
|
||||
held = false
|
||||
}
|
||||
end
|
||||
table.sort(def.controls, function(a,b)
|
||||
return #a.conditions > #b.conditions
|
||||
end)
|
||||
end
|
||||
end
|
||||
Guns4d.control_handler = Instantiatable_class:inherit(Guns4d.control_handler)
|
473
classes/Gun.lua
Normal file
473
classes/Gun.lua
Normal file
@ -0,0 +1,473 @@
|
||||
local Vec = vector
|
||||
local gun = {
|
||||
--itemstack = Itemstack
|
||||
--gun_entity = ObjRef
|
||||
name = "__template__",
|
||||
registered = {},
|
||||
properties = {
|
||||
hip = {
|
||||
offset = Vec.new(),
|
||||
},
|
||||
ads = {
|
||||
offset = Vec.new(),
|
||||
horizontal_offset = 0,
|
||||
},
|
||||
recoil = {
|
||||
velocity_correction_factor = {
|
||||
gun_axial = 1,
|
||||
player_axial = 1,
|
||||
},
|
||||
target_correction_factor = { --angular correction rate per second: time_since_fire*target_correction_factor
|
||||
gun_axial = 1,
|
||||
player_axial = 1,
|
||||
},
|
||||
angular_velocity_max = {
|
||||
gun_axial = 2,
|
||||
player_axial = 2,
|
||||
},
|
||||
angular_velocity = {
|
||||
gun_axial = {x=0, y=0},
|
||||
player_axial = {x=0, y=0},
|
||||
},
|
||||
angular_velocity_bias = {
|
||||
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
|
||||
gun_axial = 10000,
|
||||
player_axial = 10000,
|
||||
},
|
||||
},
|
||||
sway = {
|
||||
max_angle = {
|
||||
gun_axial = 0,
|
||||
player_axial = 0,
|
||||
},
|
||||
angular_velocity = {
|
||||
gun_axial = 0,
|
||||
player_axial = 0,
|
||||
},
|
||||
},
|
||||
walking_offset = {
|
||||
gun_axial = {x=.2, y=-.2},
|
||||
player_axial = {x=1,y=1},
|
||||
},
|
||||
flash_offset = Vec.new(),
|
||||
aim_time = 1,
|
||||
firerateRPM = 10000,
|
||||
controls = {}
|
||||
},
|
||||
transforms = {
|
||||
pos = Vec.new(),
|
||||
player_rotation = Vec.new(),
|
||||
dir = Vec.new(),
|
||||
--I'll need all three of them, do some precalculation.
|
||||
total_offset_rotation = {
|
||||
gun_axial = Vec.new(),
|
||||
player_axial = Vec.new(),
|
||||
},
|
||||
recoil = {
|
||||
gun_axial = Vec.new(),
|
||||
player_axial = Vec.new(),
|
||||
},
|
||||
sway = {
|
||||
gun_axial = Vec.new(),
|
||||
player_axial = Vec.new(),
|
||||
},
|
||||
walking = {
|
||||
gun_axial = Vec.new(),
|
||||
player_axial = Vec.new(),
|
||||
},
|
||||
breathing = {
|
||||
gun_axial = 1,
|
||||
player_axial = 1,
|
||||
}
|
||||
},
|
||||
velocities = {
|
||||
recoil = {
|
||||
gun_axial = Vec.new(),
|
||||
player_axial = Vec.new(),
|
||||
},
|
||||
sway = {
|
||||
gun_axial = Vec.new(),
|
||||
player_axial = Vec.new(),
|
||||
},
|
||||
},
|
||||
particle_spawners = {
|
||||
--muzzle_smoke
|
||||
},
|
||||
--magic number BEGONE
|
||||
consts = {
|
||||
HIP_PLAYER_GUN_ROT_RATIO = .75,
|
||||
HIPFIRE_BONE = "guns3d_hipfire_bone",
|
||||
AIMING_BONE = "guns3d_aiming_bone",
|
||||
HAS_RECOIL = true,
|
||||
HAS_BREATHING = true,
|
||||
HAS_SWAY = true,
|
||||
HAS_WAG = true,
|
||||
},
|
||||
walking_tick = 0,
|
||||
time_since_last_fire = 0,
|
||||
time_since_creation = 0,
|
||||
rechamber_time = 0,
|
||||
muzzle_flash = Guns4d.muzzle_flash
|
||||
}
|
||||
function gun:fire()
|
||||
if self.rechamber_time <= 0 then
|
||||
local dir = self:get_dir()
|
||||
local pos = self:get_pos()
|
||||
Guns4d.bullet_ray:new({
|
||||
player = self.player,
|
||||
pos = pos,
|
||||
dir = dir,
|
||||
range = 100,
|
||||
gun = self,
|
||||
force_mmRHA = 1,
|
||||
dropoff_mmRHA = 0
|
||||
})
|
||||
self:recoil()
|
||||
self:muzzle_flash()
|
||||
self.rechamber_time = 60/self.properties.firerateRPM
|
||||
end
|
||||
end
|
||||
function gun:recoil()
|
||||
for axis, recoil in pairs(self.velocities.recoil) do
|
||||
for _, i in pairs({"x","y"}) do
|
||||
print(i,self.properties.recoil.angular_velocity_bias[axis][i])
|
||||
recoil[i] = recoil[i] + (self.properties.recoil.angular_velocity[axis][i]*math.rand_sign((self.properties.recoil.angular_velocity_bias[axis][i]/2)+.5))
|
||||
end
|
||||
end
|
||||
self.time_since_last_fire = 0
|
||||
end
|
||||
function gun:get_dir(added_pos)
|
||||
local offset
|
||||
if added_pos then
|
||||
offset = true
|
||||
end
|
||||
added_pos = Vec.new(added_pos)
|
||||
local player = self.player
|
||||
local player_rotation = Vec.new(self.transforms.player_rotation.x, self.transforms.player_rotation.y, 0)
|
||||
local rotation = self.transforms.total_offset_rotation
|
||||
local dir = Vec.new(Vec.rotate({x=0, y=0, z=1}, {y=0, x=((rotation.gun_axial.x+rotation.player_axial.x+player_rotation.x)*math.pi/180), z=0}))
|
||||
dir = Vec.rotate(dir, {y=((rotation.gun_axial.y+rotation.player_axial.y+player_rotation.y)*math.pi/180), x=0, z=0})
|
||||
local hud_pos = dir+self:get_pos()
|
||||
if not false then
|
||||
local hud = player:hud_add({
|
||||
hud_elem_type = "image_waypoint",
|
||||
text = "muzzle_flash2.png",
|
||||
world_pos = hud_pos,
|
||||
scale = {x=10, y=10},
|
||||
alignment = {x=0,y=0},
|
||||
offset = {x=0,y=0},
|
||||
})
|
||||
minetest.after(0, function(hud)
|
||||
player:hud_remove(hud)
|
||||
end, hud)
|
||||
end
|
||||
return dir
|
||||
end
|
||||
function gun:get_pos(added_pos)
|
||||
added_pos = Vec.new(added_pos)
|
||||
local player = self.player
|
||||
local handler = self.handler
|
||||
local bone_location = Vec.new(handler.model_handler.offsets.arm.right)/10
|
||||
local gun_offset = Vec.new(self.properties.hip.offset)
|
||||
local player_rotation = Vec.new(self.transforms.player_rotation.x, self.transforms.player_rotation.y, 0)
|
||||
if handler.controls.ads then
|
||||
gun_offset = self.properties.ads.offset
|
||||
bone_location = Vec.new(0, handler:get_properties().eye_height, 0)+player:get_eye_offset()/10
|
||||
else
|
||||
--minetest is really wacky.
|
||||
bone_location = Vec.new(-bone_location.x, bone_location.y, bone_location.z)
|
||||
player_rotation.x = self.transforms.player_rotation.x*self.consts.HIP_PLAYER_GUN_ROT_RATIO
|
||||
end
|
||||
gun_offset = gun_offset+added_pos
|
||||
--dir needs to be rotated twice seperately to avoid weirdness
|
||||
local rotation = self.transforms.total_offset_rotation
|
||||
local bone_pos = Vec.rotate(bone_location, {x=0, y=player_rotation.y*math.pi/180, z=0})
|
||||
local gun_offset = Vec.rotate(Vec.rotate(gun_offset, {x=(rotation.player_axial.x+player_rotation.x)*math.pi/180,y=0,z=0}), {x=0,y=(rotation.player_axial.y+player_rotation.y)*math.pi/180,z=0})
|
||||
--[[local hud_pos = bone_pos+gun_offset+handler:get_pos()
|
||||
if not false then
|
||||
local hud = player:hud_add({
|
||||
hud_elem_type = "image_waypoint",
|
||||
text = "muzzle_flash2.png",
|
||||
world_pos = hud_pos,
|
||||
scale = {x=10, y=10},
|
||||
alignment = {x=0,y=0},
|
||||
offset = {x=0,y=0},
|
||||
})
|
||||
minetest.after(0, function(hud)
|
||||
player:hud_remove(hud)
|
||||
end, hud)
|
||||
end]]
|
||||
--world pos, position of bone, offset of gun from bone (with added_pos)
|
||||
return bone_pos+gun_offset+handler:get_pos(), bone_pos, gun_offset
|
||||
end
|
||||
function gun: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
|
||||
obj:on_step()
|
||||
end
|
||||
function gun: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:update(dt)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if not self:has_entity() then self:add_entity() end
|
||||
self.dir = self:get_dir()
|
||||
self.pos = self:get_pos()
|
||||
local handler = self.handler
|
||||
local look_rotation = {x=handler.look_rotation.x,y=handler.look_rotation.y}
|
||||
local total_rot = self.transforms.total_offset_rotation
|
||||
local player_rot = self.transforms.player_rotation
|
||||
local constant = 1.4
|
||||
|
||||
--player look rotation
|
||||
local next_vert_aim = ((player_rot.x+look_rotation.x)/(1+((constant*10)*dt)))-look_rotation.x
|
||||
if math.abs(look_rotation.x-next_vert_aim) > .005 then
|
||||
player_rot.x = next_vert_aim
|
||||
else
|
||||
player_rot.x = look_rotation.x
|
||||
end
|
||||
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.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
|
||||
player_rot.y = -handler.look_rotation.y
|
||||
local offsets = self.transforms
|
||||
total_rot.player_axial = offsets.recoil.player_axial + offsets.walking.player_axial + offsets.sway.player_axial + {x=offsets.breathing.player_axial,y=0,z=0} + {x=0,y=0,z=0}
|
||||
total_rot.gun_axial = offsets.recoil.gun_axial + offsets.walking.gun_axial + offsets.sway.gun_axial
|
||||
if self.handler.controls.ads then
|
||||
if not self.useless_hud then
|
||||
self.useless_hud = {}
|
||||
self.useless_hud.reticle = self.player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x=.5,y=.5},
|
||||
scale = {x=1.5,y=1.5},
|
||||
text = "gun_mrkr.png",
|
||||
}
|
||||
self.useless_hud.fore = self.player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x=.5,y=.5},
|
||||
scale = {x=10,y=10},
|
||||
text = "scope_fore.png",
|
||||
}
|
||||
self.useless_hud.back = self.player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x=.5,y=.5},
|
||||
scale = {x=10,y=10},
|
||||
text = "scope_back.png",
|
||||
}
|
||||
end
|
||||
local wininfo = minetest.get_player_window_information(self.player:get_player_name())
|
||||
if wininfo then
|
||||
local rot = total_rot.player_axial+total_rot.gun_axial
|
||||
local ratio = wininfo.size.x/wininfo.size.y
|
||||
local offset_y = ((-rot.y/(80*2))+.5)
|
||||
local offset_x = (((-rot.x*ratio)/(80*2))+.5)
|
||||
self.player:hud_change(self.useless_hud.reticle, "position", {x=offset_y, y=offset_x})
|
||||
self.player:hud_change(self.useless_hud.fore, "position", {x=offset_y, y=offset_x})
|
||||
self.player:hud_change(self.useless_hud.back, "position", {x=((4*total_rot.player_axial.y/(80*2))+.5), y=(((4*total_rot.player_axial.x*ratio)/(80*2))+.5)})
|
||||
end
|
||||
elseif self.useless_hud then
|
||||
for i, v in pairs(self.useless_hud) do
|
||||
self.player:hud_remove(v)
|
||||
end
|
||||
self.useless_hud = nil
|
||||
end
|
||||
end
|
||||
function gun:update_wag(dt)
|
||||
local handler = self.handler
|
||||
if handler.walking then
|
||||
self.walking_tick = self.walking_tick + (dt*Vec.length(self.player:get_velocity()))
|
||||
else
|
||||
self.walking_tick = 0
|
||||
end
|
||||
local walking_offset = self.transforms.walking
|
||||
for _, i in pairs({"x","y"}) do
|
||||
for axis, _ in pairs(walking_offset) do
|
||||
if handler.walking then
|
||||
local time = self.walking_tick
|
||||
local multiplier = 1
|
||||
if i == "x" then
|
||||
multiplier = 2
|
||||
end
|
||||
print(dump(self.properties.walking_offset[axis]))
|
||||
walking_offset[axis][i] = math.sin((time/1.6)*math.pi*multiplier)*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
|
||||
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
|
||||
function gun:update_recoil(dt)
|
||||
for axis, _ in pairs(self.transforms.recoil) do
|
||||
for _, i in pairs({"x","y"}) do
|
||||
local recoil = self.transforms.recoil[axis][i]
|
||||
local recoil_vel = 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
|
||||
if math.abs(recoil_vel) > 0.01 then
|
||||
--look, I know this doesn't really make sense, but this is the best I can do atm. I've looked for better and mroe intuitive methods, I cannot find them.
|
||||
--8-(8*(1-(8/100))
|
||||
--recoil_vel = recoil_vel-((recoil_vel-(recoil_vel/(1+self.properties.recoil.velocity_correction_factor[axis])))*dt*10)
|
||||
recoil_vel = recoil_vel * (recoil_vel/(recoil_vel/(self.properties.recoil.velocity_correction_factor[axis]*2))*dt)
|
||||
else
|
||||
recoil_vel = 0
|
||||
end
|
||||
if math.abs(recoil_vel)>math.abs(old_recoil_vel) then
|
||||
recoil_vel = 0
|
||||
end
|
||||
--ax^2+bx+c
|
||||
--recoil_velocity_correction_rate
|
||||
--recoil_correction_rate
|
||||
local old_recoil = recoil
|
||||
if math.abs(recoil) > 0.001 then
|
||||
local correction_multiplier = self.time_since_last_fire*self.properties.recoil.target_correction_factor[axis]
|
||||
local correction_value = recoil*correction_multiplier
|
||||
correction_value = math.clamp(math.abs(correction_value), 0, self.properties.recoil.target_correction_max_rate[axis])
|
||||
recoil=recoil-(correction_value*dt*(math.abs(recoil)/recoil))
|
||||
--prevent overcorrection
|
||||
if math.abs(recoil) > math.abs(old_recoil) then
|
||||
recoil = 0
|
||||
end
|
||||
end
|
||||
self.velocities.recoil[axis][i] = recoil_vel
|
||||
self.transforms.recoil[axis][i] = recoil
|
||||
end
|
||||
end
|
||||
end
|
||||
function gun: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.
|
||||
if x > math.pi*(8/9) then
|
||||
self.transforms.breathing.player_axial=self.transforms.breathing.player_axial-(self.transforms.breathing.player_axial*2*dt)
|
||||
else
|
||||
self.transforms.breathing.player_axial = scale*(math.sin(x))
|
||||
end
|
||||
end
|
||||
function gun:update_sway(dt)
|
||||
for axis, sway in pairs(self.transforms.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
|
||||
sway_vel = Vec.normalize(sway_vel+(ran*dt))*self.properties.sway.angular_velocity[axis]
|
||||
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]
|
||||
sway_vel = Vec.new()
|
||||
end
|
||||
self.transforms.sway[axis] = sway
|
||||
self.velocities.sway[axis] = sway_vel
|
||||
end
|
||||
print(self.transforms.sway)
|
||||
end
|
||||
function gun:prepare_deletion()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if self:has_entity() then self.entity:remove() end
|
||||
end
|
||||
gun.construct = function(def)
|
||||
if def.instance then
|
||||
--remember to give gun an id
|
||||
assert(def.itemstack, "no itemstack provided for initialized object")
|
||||
assert(def.player, "no player provided")
|
||||
local meta = def.itemstack:get_meta()
|
||||
if meta:get_string("guns4d_id") == "" then
|
||||
local id = tostring(Unique_id.generate())
|
||||
meta:set_string("guns4d_id", id)
|
||||
def.player:set_wielded_item(def.itemstack)
|
||||
def.id = id
|
||||
else
|
||||
def.id = meta:get_string("guns4d_id")
|
||||
end
|
||||
--make sure there's nothing missing
|
||||
def.properties = table.fill(gun.properties, def.properties)
|
||||
--Vecize
|
||||
def.transforms = table.deep_copy(gun.transforms)
|
||||
for i, tbl in pairs(def.transforms) do
|
||||
if tbl.gun_axial and tbl.player_axial and (not i=="breathing") then
|
||||
tbl.gun_axial = Vec.new(tbl.gun_axial)
|
||||
tbl.player_axial = Vec.new(tbl.player_axial)
|
||||
end
|
||||
end
|
||||
def.velocities = table.deep_copy(gun.velocities)
|
||||
for i, tbl in pairs(def.velocities) do
|
||||
if tbl.gun_axial and tbl.player_axial then
|
||||
tbl.gun_axial = Vec.new(tbl.gun_axial)
|
||||
tbl.player_axial = Vec.new(tbl.player_axial)
|
||||
end
|
||||
end
|
||||
elseif def.name ~= "__template__" then
|
||||
local props = def.properties
|
||||
assert(def.name, "no name provided")
|
||||
assert(def.itemstring, "no itemstring provided")
|
||||
--(this tableref is ephermeral after constructor is called, see instantiatable_class)
|
||||
Guns4d.gun.registered[def.name] = def
|
||||
minetest.register_entity(def.name.."_visual", {
|
||||
initial_properties = {
|
||||
visual = "mesh",
|
||||
mesh = props.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
|
||||
local handler = Guns4d.players[player:get_player_name()].handler
|
||||
local lua_object = handler.gun
|
||||
if not lua_object then obj:remove() return end
|
||||
--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.controls.ads then
|
||||
local pitch = lua_object.transforms.total_offset_rotation.player_axial.x+lua_object.transforms.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.transforms.total_offset_rotation.gun_axial+axial_modifier
|
||||
--attach to the correct bone, and rotate
|
||||
if handler.controls.ads == false then
|
||||
local normal_pos = Vec.new(props.hip.offset)*10
|
||||
-- Vec.multiply({x=normal_pos.x, y=normal_pos.z, z=-normal_pos.y}, 10)
|
||||
obj:set_attach(player, gun.consts.HIPFIRE_BONE, normal_pos, -axial_rot, true)
|
||||
else
|
||||
local normal_pos = (props.ads.offset+Vec.new(props.ads.horizontal_offset,0,0))*10
|
||||
obj:set_attach(player, gun.consts.AIMING_BONE, normal_pos, -axial_rot, true)
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
end
|
||||
Guns4d.gun = Instantiatable_class:inherit(gun)
|
35
classes/Instantiatable_class.lua
Normal file
35
classes/Instantiatable_class.lua
Normal file
@ -0,0 +1,35 @@
|
||||
Instantiatable_class = {
|
||||
instance = false
|
||||
}
|
||||
--not that construction change is NOT called for inheriting an object.
|
||||
function Instantiatable_class:inherit(def)
|
||||
--construction chain for inheritance
|
||||
if not def then def = {} else def = table.shallow_copy(def) end
|
||||
def.instance = false
|
||||
def._construct_low = def.construct
|
||||
--this effectively creates a construction chain by overwriting .construct
|
||||
function def.construct(parameters)
|
||||
--rawget because in a instance it may only be present in a hierarchy but not the table itself
|
||||
if rawget(def, "_construct_low") then
|
||||
def._construct_low(parameters)
|
||||
end
|
||||
if self.construct then
|
||||
self.construct(parameters)
|
||||
end
|
||||
end
|
||||
def.construct(def)
|
||||
--iterate through table properties
|
||||
setmetatable(def, {__index = self})
|
||||
return def
|
||||
end
|
||||
function Instantiatable_class:new(def)
|
||||
if not def then def = {} else def = table.shallow_copy(def) end
|
||||
def.instance = true
|
||||
function def:inherit(def)
|
||||
assert(false, "cannot inherit instantiated object")
|
||||
end
|
||||
setmetatable(def, {__index = self})
|
||||
--call the construct chain for inherited objects, also important this is called after meta changes
|
||||
self.construct(def)
|
||||
return def
|
||||
end
|
195
classes/Player_handler.lua
Normal file
195
classes/Player_handler.lua
Normal file
@ -0,0 +1,195 @@
|
||||
local Vec = vector
|
||||
local default_active_controls = {
|
||||
ads = false
|
||||
}
|
||||
local player_handler = {
|
||||
--player = playerref
|
||||
--name = playername
|
||||
--wielded_item = ItemStack
|
||||
--gun = Gun (class)
|
||||
--wield_index = Int
|
||||
--model_handler = player_model_handler
|
||||
look_rotation = {x=0, y=0},
|
||||
look_offset = Vec.new(),
|
||||
ads_location = 0,
|
||||
controls = {},
|
||||
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
|
||||
self.wielded_item = self.player:get_wielded_item()
|
||||
local held_gun = self:holding_gun() --get the gun class that is associated with the held gun
|
||||
if held_gun then
|
||||
--was there a gun last time? did the wield index change?
|
||||
local old_index = self.wield_index
|
||||
self.wield_index = player:get_wield_index()
|
||||
--if gun has changed or was not held, then reset.
|
||||
if (not self.gun) or (self.gun.id ~= self.wielded_item:get_meta():get_string("guns4d_id")) then
|
||||
--initialize all handlers
|
||||
----gun handler----
|
||||
if self.gun then --delete gun object if present
|
||||
self.gun:prepare_deletion()
|
||||
self.gun = nil
|
||||
end
|
||||
self.gun = held_gun:new({itemstack=self.wielded_item, player=self.player, handler=self}) --this will set itemstack meta, and create the gun based off of meta and other data.
|
||||
----model handler----
|
||||
if self.model_handler then --if model_handler present, then delete
|
||||
self.model_handler:prepare_deletion()
|
||||
self.model_handler = nil
|
||||
end
|
||||
self.model_handler = model_handler.get_handler(self:get_properties().mesh):new({player=self.player})
|
||||
----control handler----
|
||||
self.control_handler = Guns4d.control_handler:new({player=player, controls=self.gun.properties.controls})
|
||||
--reinitialize some handler data and set set_hud_flags
|
||||
self.horizontal_offset = self.gun.properties.ads.horizontal_offset
|
||||
player:hud_set_flags({wielditem = false, crosshair = false})
|
||||
end
|
||||
self.look_rotation.x, self.look_rotation.y = player:get_look_vertical()*180/math.pi, -player:get_look_horizontal()*180/math.pi
|
||||
--update handlers
|
||||
self.control_handler:update(dt)
|
||||
self.model_handler:update(dt)
|
||||
self.gun:update(dt)
|
||||
elseif self.gun then
|
||||
self.control_handler = nil
|
||||
--delete gun object
|
||||
self.gun:prepare_deletion()
|
||||
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
|
||||
player:hud_set_flags({wielditem = true, crosshair = true}) --reenable hud elements
|
||||
end
|
||||
--eye offsets
|
||||
if self.controls.ads and (self.ads_location<1) then
|
||||
self.ads_location = math.clamp(self.ads_location + (dt/self.gun.properties.aim_time), 0, 1)
|
||||
elseif (not self.controls.ads) and self.ads_location>0 then
|
||||
local divisor = .4
|
||||
if self.gun then
|
||||
divisor = self.gun.properties.aim_time
|
||||
end
|
||||
self.ads_location = math.clamp(self.ads_location - (dt/divisor), 0, 1)
|
||||
end
|
||||
self.look_offset.x = self.horizontal_offset*self.ads_location
|
||||
player:set_eye_offset(self.look_offset*10)
|
||||
--some status stuff
|
||||
if TICK % 2 == 0 then
|
||||
self.touching_ground = self:get_is_on_ground()
|
||||
end
|
||||
self.walking = self:get_is_walking()
|
||||
--stored properties and pos must be reset as they could be outdated.
|
||||
self.properties = nil
|
||||
self.pos = nil
|
||||
end
|
||||
function player_handler:get_is_on_ground()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local touching_ground = false
|
||||
local player = self.player
|
||||
local player_properties = self:get_properties()
|
||||
local ray = minetest.raycast(self:get_pos()+vector.new(0, self:get_properties().eye_height, 0), self:get_pos()-vector.new(0,.1,0), true, true)
|
||||
for pointed_thing in ray do
|
||||
if pointed_thing.type == "object" then
|
||||
if pointed_thing.ref ~= player and pointed_thing.ref:get_properties().physical == true then
|
||||
touching_ground = true
|
||||
end
|
||||
end
|
||||
if pointed_thing.type == "node" then
|
||||
touching_ground = true
|
||||
end
|
||||
end
|
||||
return touching_ground
|
||||
end
|
||||
function player_handler:get_is_walking()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local walking = false
|
||||
local velocity = self.player:get_velocity()
|
||||
local controls
|
||||
if not self.control_handler then
|
||||
controls = self.player:get_player_control()
|
||||
else
|
||||
controls = self.control_handler.player_pressed
|
||||
end
|
||||
if (vector.length(vector.new(velocity.x, 0, velocity.z)) > .1) and (controls.up or controls.down or controls.left or controls.right) and self.touching_ground then
|
||||
walking = true
|
||||
end
|
||||
return walking
|
||||
end
|
||||
--resets the controls bools table for the player_handler
|
||||
function player_handler:reset_controls_table()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
self.controls = table.deep_copy(default_active_controls)
|
||||
end
|
||||
--doubt I'll ever use this... but just in case I don't want to forget.
|
||||
function player_handler:get_pos()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if self.pos then return self.pos end
|
||||
self.pos = self.player:get_pos()
|
||||
return self.pos
|
||||
end
|
||||
function player_handler:set_pos(val)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
self.pos = vector.new(val)
|
||||
self.player:set_pos(val)
|
||||
end
|
||||
function player_handler:get_properties()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if self.properties then return self.properties end
|
||||
self.properties = self.player:get_properties()
|
||||
return self.properties
|
||||
end
|
||||
function player_handler:set_properties(properties)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
self.player:set_properties(properties)
|
||||
self.properties = table.fill(self.properties, properties)
|
||||
end
|
||||
function player_handler:holding_gun()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if self.wielded_item then
|
||||
for name, obj in pairs(Guns4d.gun.registered) do
|
||||
if obj.itemstring == self.wielded_item:get_name() then
|
||||
return obj
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function player_handler:update_wield_item(wield, meta)
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
local stack = self.wielded_item
|
||||
if wield then
|
||||
stack = ItemStack(wield)
|
||||
end
|
||||
if meta then
|
||||
local tbl = meta
|
||||
if type(meta) ~= "table" then
|
||||
tbl = meta:to_table()
|
||||
end
|
||||
stack:from_table(tbl)
|
||||
end
|
||||
self.player:set_wielded_item(stack)
|
||||
return stack
|
||||
end
|
||||
function player_handler:prepare_deletion()
|
||||
assert(self.instance, "attempt to call object method on a class")
|
||||
if self.gun then
|
||||
self.gun:prepare_deletion()
|
||||
self.gun = nil
|
||||
end
|
||||
end
|
||||
--note that construct is NOT called as a method
|
||||
function player_handler.construct(def)
|
||||
if def.instance then
|
||||
def.old_mesh = def.player:get_properties().mesh
|
||||
assert(def.player, "no player obj provided to player_handler on construction")
|
||||
--this is important, as setting a value within a table would set it for all tables otherwise
|
||||
for i, v in pairs(player_handler) do
|
||||
if (type(v) == "table") and not def[i] then
|
||||
def[i] = v
|
||||
end
|
||||
end
|
||||
def.look_rotation = table.deep_copy(player_handler.look_rotation)
|
||||
def.controls = table.deep_copy(default_active_controls)
|
||||
end
|
||||
end
|
||||
Guns4d.player_handler = Instantiatable_class:inherit(player_handler)
|
78
classes/Player_model_handler.lua
Normal file
78
classes/Player_model_handler.lua
Normal file
@ -0,0 +1,78 @@
|
||||
local Vec = vector
|
||||
--[[offsets = {
|
||||
head = vector.new(0,6.3,0),
|
||||
arm_right = vector.new(-3.15, 5.5, 0),
|
||||
arm_right_global = vector.new(-3.15, 11.55, 0), --can be low precision
|
||||
arm_left = vector.new(3.15, 5.5, 0),
|
||||
arm_left_global = vector.new(3.15, 11.55, 0),
|
||||
}]]
|
||||
Guns4d.player_model_handler = {
|
||||
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)
|
||||
},
|
||||
head = Vec.new(0,6.3,0)
|
||||
},
|
||||
handlers = {},
|
||||
mesh = "guns3d_character.b3d"
|
||||
}
|
||||
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
|
||||
end
|
||||
function player_model:get_handler(meshname)
|
||||
local selected_handler = player_model.handlers[meshname]
|
||||
if selected_handler then return selected_handler end
|
||||
return player_model.default_handler
|
||||
end
|
||||
function player_model:update()
|
||||
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 gun = handler.gun
|
||||
local player_axial_offset = gun.transforms.total_offset_rotation.player_axial
|
||||
local pitch = player_axial_offset.x+gun.transforms.player_rotation.x
|
||||
local combined = player_axial_offset+gun.transforms.total_offset_rotation.gun_axial+Vec.new(gun.transforms.player_rotation.x,0,0)
|
||||
local eye_pos = vector.new(0, handler:get_properties().eye_height*10, 0)
|
||||
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_aiming_bone", eye_pos, vector.new(pitch, 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})
|
||||
end
|
||||
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
|
||||
local properties = handler:get_properties()
|
||||
if minetest.get_modpath("player_api") then
|
||||
player_api.set_model(self.player, self.old)
|
||||
end
|
||||
properties.mesh = self.old
|
||||
handler:set_properties(properties)
|
||||
end
|
||||
---@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.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)
|
||||
else
|
||||
if def.replace then
|
||||
player_model.handlers[def.replace] = def
|
||||
end
|
||||
end
|
||||
end
|
||||
Guns4d.player_model_handler = Instantiatable_class:inherit(player_model)
|
||||
Guns4d.player_model_handler:set_default_handler()
|
||||
|
159
classes/raycast_defunked.lua
Normal file
159
classes/raycast_defunked.lua
Normal file
@ -0,0 +1,159 @@
|
||||
function guns3d.ray(player, pos, dir, def, bullet_info)
|
||||
--"transverse" just means in a node
|
||||
--"free" means in open air
|
||||
local playername = player:get_player_name()
|
||||
local is_first_iter = false
|
||||
local constant = .7
|
||||
local normal
|
||||
----------------------------------------------------------initialize------------------------------------------------------------------
|
||||
if not bullet_info then
|
||||
is_first_iter = true
|
||||
bullet_info = {
|
||||
history = {},
|
||||
state = "free",
|
||||
last_pos = pos,
|
||||
last_node = "",
|
||||
last_normal = vector.new(),
|
||||
end_direction = dir,
|
||||
range_left = def.bullet.range,
|
||||
penetrating_force = def.bullet.penetration_RHA
|
||||
--last_pointed
|
||||
}
|
||||
end
|
||||
table.insert(bullet_info.history, {start_pos=pos, state=bullet_info.state, normal=bullet_info.last_normal, end_direction = bullet_info.end_direction})
|
||||
--set ray end
|
||||
local pos2 = pos+(dir*bullet_info.range_left)
|
||||
local block_ends_early = false
|
||||
--if was last in a block, check where the "transverse" state should end.
|
||||
--------------------------------------------------prepare for raycast --------------------------------------------------------------
|
||||
if bullet_info.state == "transverse" then
|
||||
local pointed
|
||||
local ray = minetest.raycast(pos+(dir*(constant+.01)), pos, false, false)
|
||||
for p in ray do
|
||||
if p.type == "node" and (table.compare(p.under, bullet_info.last_pointed.under) or not minetest.registered_nodes[minetest.get_node(bullet_info.last_pointed.under).name].node_box) then
|
||||
pointed = p
|
||||
break
|
||||
end
|
||||
end
|
||||
--maybe remove check for pointed
|
||||
if pointed and vector.distance(pointed.intersection_point, pos) < constant then
|
||||
pos2 = pointed.intersection_point
|
||||
block_ends_early = true
|
||||
normal = pointed.intersection_normal
|
||||
bullet_info.end_direction = vector.direction(dir, vector.new())
|
||||
else
|
||||
pos2 = pos+(dir*constant)
|
||||
end
|
||||
end
|
||||
-----------------------------------------------------------raycast--------------------------------------------------------------
|
||||
local ray = minetest.raycast(pos, pos2, true, true)
|
||||
local pointed
|
||||
local next_ray_pos = pos2
|
||||
for p in ray do
|
||||
if vector.distance(p.intersection_point, bullet_info.last_pos) > 0.0005 and vector.distance(p.intersection_point, bullet_info.last_pos) < bullet_info.range_left then
|
||||
local distance = vector.distance(pos, p.intersection_point)
|
||||
--if it's a node, check that it's note supposed to be ignored according to it's generated properties
|
||||
if p.type == "node" and guns3d.node_properties[minetest.get_node(p.under).name].behavior ~= "ignore" then
|
||||
local next_penetration_val = bullet_info.penetrating_force-(distance*guns3d.node_properties[minetest.get_node(p.under).name].rha*1000)
|
||||
if bullet_info.state ~= "transverse" then
|
||||
pointed = p
|
||||
--print(dump(p))
|
||||
bullet_info.state = "transverse"
|
||||
next_ray_pos = p.intersection_point
|
||||
else
|
||||
pointed = p
|
||||
if minetest.get_node(p.under).name ~= bullet_info.last_node and next_penetration_val > 0 and guns3d.node_properties[minetest.get_node(p.under).name].behavior ~= "ignore" then
|
||||
next_ray_pos = p.intersection_point
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
--if it's an object, make sure it's not the player object
|
||||
--note that while it may seem like this will create a infinite hit loop, it resolves itself as the intersection_point of the next ray will be close enough as to skip the pointed. See first line of iterator.
|
||||
if p.type == "object" and p.ref ~= player then
|
||||
--apply force dropoff
|
||||
local next_penetration_val = bullet_info.penetrating_force-def.bullet.penetration_dropoff_RHA*distance
|
||||
if bullet_info.state == "transverse" then
|
||||
next_penetration_val = bullet_info.penetrating_force-(distance*guns3d.node_properties[minetest.get_node(bullet_info.last_pointed.under).name].rha*1000)
|
||||
end
|
||||
--insure there's still penetrating force left to actually damage the player
|
||||
if bullet_info.penetrating_force > 0 then
|
||||
if (bullet_info.state == "transverse" and next_penetration_val > 0) or (bullet_info.state == "free" and bullet_info.penetrating_force-def.bullet.penetration_dropoff_RHA*distance > 0) then
|
||||
local penetration_val = next_penetration_val
|
||||
if bullet_info.state == "free" then
|
||||
bullet_info.penetrating_force = next_penetration_val
|
||||
penetration_val = bullet_info.penetrating_force
|
||||
end
|
||||
local damage = math.floor((def.bullet.damage*(next_penetration_val/def.bullet.penetration_RHA))+1)
|
||||
p.ref:punch(player, nil, {damage_groups = {fleshy = damage}}, dir)
|
||||
if p.ref:is_player() then
|
||||
--TODO: finish
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
---------------------prepare for recursion---------------------------------------------------------------------------------
|
||||
local penetration_loss = def.bullet.penetration_dropoff_RHA
|
||||
local distance = vector.distance(pos, next_ray_pos)
|
||||
local new_dir = dir
|
||||
local node_properties
|
||||
if pointed then
|
||||
node_properties = guns3d.node_properties[minetest.get_node(pointed.under).name]
|
||||
end
|
||||
if pointed and (not normal) then
|
||||
normal = pointed.intersection_normal
|
||||
else
|
||||
normal = vector.new()
|
||||
end
|
||||
if not bullet_info.end_direction then
|
||||
bullet_info.end_direction = new_dir
|
||||
end
|
||||
--we know if the first raycast didn't find it ended early, or if there wasn't a hit, that it isn't in a block
|
||||
if block_ends_early or not pointed then
|
||||
bullet_info.state = "free"
|
||||
end
|
||||
--calculate penetration loss, and simulate loss of accuracy
|
||||
if bullet_info.history[#bullet_info.history].state == "transverse" and pointed then
|
||||
local rotation = vector.apply(vector.new(), function(a)
|
||||
a=a+(((math.random()-.5)*2)*node_properties.random_deviation*def.bullet.penetration_deviation*distance)
|
||||
return a
|
||||
end)
|
||||
new_dir = vector.rotate(new_dir, rotation*math.pi/180)
|
||||
penetration_loss = node_properties.rha*1000
|
||||
end
|
||||
--set the current bullet info.
|
||||
bullet_info.penetrating_force=bullet_info.penetrating_force-(penetration_loss*distance)
|
||||
bullet_info.range_left = bullet_info.range_left-distance
|
||||
bullet_info.last_pointed = pointed
|
||||
bullet_info.last_normal = normal
|
||||
bullet_info.last_pos = pos
|
||||
|
||||
--set the last node
|
||||
if pointed then
|
||||
bullet_info.last_node = minetest.get_node(pointed.under).name
|
||||
end
|
||||
--recurse.
|
||||
if bullet_info.range_left > 0.001 and bullet_info.penetrating_force > 0 then
|
||||
guns3d.ray(player, next_ray_pos, new_dir, def, bullet_info)
|
||||
end
|
||||
-------------------------- visual -------------------------------------------------------------------------------------
|
||||
if is_first_iter then
|
||||
for i, val in pairs(bullet_info.history) do
|
||||
if not table.compare(val.normal, vector.new()) then
|
||||
guns3d.handle_node_hit_fx(val.normal, val.end_direction, val.start_pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local raycast = {
|
||||
history = {},
|
||||
state = "free",
|
||||
last_pos = pos,
|
||||
last_node = "",
|
||||
last_normal = vector.new(),
|
||||
end_direction = dir,
|
||||
range_left = def.bullet.range,
|
||||
penetrating_force = def.bullet.penetration_RHA
|
||||
}
|
98
gun_api.lua
Normal file
98
gun_api.lua
Normal file
@ -0,0 +1,98 @@
|
||||
local Vec = vector
|
||||
local default_def = {
|
||||
--name = <string>
|
||||
--itemstring = <string>
|
||||
--textures = {<textures>}
|
||||
--mesh = <meshname> (media)
|
||||
hip = {
|
||||
offset = Vec.new(0,0,.2),
|
||||
},
|
||||
ads = {
|
||||
offset = Vec.new(0,0,.1),
|
||||
horizontal_offset = .1,
|
||||
},
|
||||
recoil = {
|
||||
velocity_correction_factor = {
|
||||
gun_axial = 2,
|
||||
player_axial = 2,
|
||||
},
|
||||
target_correction_factor = { --angular correction rate per second: time_since_fire*target_correction_factor
|
||||
gun_axial = 30,
|
||||
player_axial = 1,
|
||||
},
|
||||
target_correction_max_rate = { --the cap for time_since_fire*target_correction_factor
|
||||
gun_axial = 100,
|
||||
player_axial = 6,
|
||||
},
|
||||
angular_velocity_max = {
|
||||
gun_axial = 0,
|
||||
player_axial = 0,
|
||||
},
|
||||
angular_velocity = {
|
||||
gun_axial = {x=.2, y=.25},
|
||||
player_axial = {x=.25, y=.4},
|
||||
},
|
||||
},
|
||||
firerateRPM = 600,
|
||||
controls = {
|
||||
aim = {
|
||||
conditions = {"RMB"},
|
||||
loop = false,
|
||||
timer = 0,
|
||||
func = function(active, interrupted, data, busy_list, handler)
|
||||
if active then
|
||||
handler.controls.ads = not handler.controls.ads
|
||||
end
|
||||
end
|
||||
},
|
||||
fire = {
|
||||
conditions = {"LMB"},
|
||||
loop = true,
|
||||
timer = 0,
|
||||
func = function(active, interrupted, data, busy_list, handler)
|
||||
handler.gun:fire()
|
||||
end
|
||||
}
|
||||
},
|
||||
consts = {
|
||||
HIP_PLAYER_GUN_ROT_RATIO = .6
|
||||
},
|
||||
aim_time = .5
|
||||
}
|
||||
local valid_ctrls = {
|
||||
up=true,
|
||||
down=true,
|
||||
left=true,
|
||||
right=true,
|
||||
jump=true,
|
||||
aux1=true,
|
||||
sneak=true,
|
||||
dig=true,
|
||||
place=true,
|
||||
LMB=true,
|
||||
RMB=true,
|
||||
zoom=true,
|
||||
}
|
||||
function Guns4d.register_gun_default(def)
|
||||
assert(def, "no definition table provided")
|
||||
assert(def.name, "no name provided when registering gun")
|
||||
assert(def.itemstring, "no itemstring provided when registering gun")
|
||||
local new_def = {}
|
||||
new_def.consts = def.consts
|
||||
new_def.name = def.name; def.name = nil
|
||||
new_def.itemstring = def.itemstring; def.itemstring = nil
|
||||
new_def.properties = table.fill(default_def, def)
|
||||
--validate controls
|
||||
if new_def.properties.controls then
|
||||
for i, control in pairs(new_def.properties.controls) do
|
||||
assert(control.conditions, "no conditions provided for control")
|
||||
for _, condition in pairs(control.conditions) do
|
||||
if not valid_ctrls[condition] then
|
||||
assert(false, "invalid key: '"..condition.."'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--gun is registered within this function
|
||||
Guns4d.gun:inherit(new_def)
|
||||
end
|
51
init.lua
Normal file
51
init.lua
Normal file
@ -0,0 +1,51 @@
|
||||
local Vec = vector
|
||||
Guns4d = {
|
||||
players = {}
|
||||
}
|
||||
local path = minetest.get_modpath("guns4d")
|
||||
dofile(path.."/misc_helpers.lua")
|
||||
dofile(path.."/visual_effects.lua")
|
||||
dofile(path.."/gun_api.lua")
|
||||
dofile(path.."/block_values.lua")
|
||||
path = path .. "/classes"
|
||||
dofile(path.."/Instantiatable_class.lua")
|
||||
dofile(path.."/Bullet_ray.lua")
|
||||
dofile(path.."/Control_handler.lua")
|
||||
dofile(path.."/Gun.lua")
|
||||
dofile(path.."/Player_model_handler.lua")
|
||||
dofile(path.."/Player_handler.lua")
|
||||
|
||||
--load after
|
||||
path = minetest.get_modpath("guns4d")
|
||||
|
||||
|
||||
local player_handler = Guns4d.player_handler
|
||||
local gun = Guns4d.gun
|
||||
|
||||
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local pname = player:get_player_name()
|
||||
Guns4d.players[pname] = {
|
||||
handler = player_handler:new({player=player})
|
||||
}
|
||||
player:set_fov(80)
|
||||
end)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local pname = player:get_player_name()
|
||||
Guns4d.players[pname].handler:prepare_deletion()
|
||||
Guns4d.players[pname] = nil
|
||||
end)
|
||||
TICK = 0
|
||||
minetest.register_globalstep(function(dt)
|
||||
TICK = TICK + 1
|
||||
if TICK > 100000 then TICK = 0 end
|
||||
for player, obj in pairs(Guns4d.players) do
|
||||
if not obj.handler then
|
||||
--spawn the player handler. The player handler handles the gun(s),
|
||||
--the player's model, and controls
|
||||
obj.handler = player_handler:new({player=player})
|
||||
end
|
||||
obj.handler:update(dt)
|
||||
end
|
||||
end)
|
129
misc_helpers.lua
Normal file
129
misc_helpers.lua
Normal file
@ -0,0 +1,129 @@
|
||||
--can't be copyright claimed by myself, luckily... well actually knowing the legal system I probably could sue myself.
|
||||
Unique_id = {
|
||||
generated = {},
|
||||
}
|
||||
function math.clamp(val, lower, upper)
|
||||
if lower > upper then lower, upper = upper, lower end
|
||||
return math.max(lower, math.min(upper, val))
|
||||
end
|
||||
function Unique_id.generate()
|
||||
local genned_ids = Unique_id.generated
|
||||
local id = string.sub(tostring(math.random()), 3)
|
||||
while genned_ids[id] do
|
||||
id = string.sub(tostring(math.random()), 3)
|
||||
end
|
||||
genned_ids[id] = true
|
||||
return id
|
||||
end
|
||||
function math.rand_sign(b)
|
||||
b = b or .5
|
||||
local int = 1
|
||||
if math.random() > b then int=-1 end
|
||||
return int
|
||||
end
|
||||
--for table vectors that aren't vector objects
|
||||
---@diagnostic disable-next-line: lowercase-global
|
||||
function tolerance_check(a,b,tolerance)
|
||||
return math.abs(a-b) > tolerance
|
||||
end
|
||||
function vector.equals_tolerance(v, vb, tolerance)
|
||||
tolerance = tolerance or 0
|
||||
return (
|
||||
tolerance_check(v.x, vb.x, tolerance) and
|
||||
tolerance_check(v.y, vb.y, tolerance) and
|
||||
tolerance_check(v.z, vb.z, tolerance)
|
||||
)
|
||||
end
|
||||
--copy everything
|
||||
function table.deep_copy(tbl, copy_metatable, indexed_tables)
|
||||
if not indexed_tables then indexed_tables = {} end
|
||||
local new_table = {}
|
||||
local metat = getmetatable(tbl)
|
||||
if metat then
|
||||
if copy_metatable then
|
||||
setmetatable(new_table, table.deep_copy(metat, true))
|
||||
else
|
||||
setmetatable(new_table, metat)
|
||||
end
|
||||
end
|
||||
for i, v in pairs(tbl) do
|
||||
if type(v) == "table" then
|
||||
if not indexed_tables[v] then
|
||||
indexed_tables[v] = true
|
||||
new_table[i] = table.deep_copy(v, copy_metatable)
|
||||
end
|
||||
else
|
||||
new_table[i] = v
|
||||
end
|
||||
end
|
||||
return new_table
|
||||
end
|
||||
--replace elements in tbl with elements in replacement, but preserve the rest
|
||||
function table.fill(tbl, replacement, preserve_reference, indexed_tables)
|
||||
if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing
|
||||
local new_table = tbl
|
||||
if not preserve_reference then
|
||||
new_table = table.deep_copy(tbl)
|
||||
end
|
||||
for i, v in pairs(replacement) do
|
||||
if new_table[i] then
|
||||
if type(v) == "table" and type(replacement[i]) == "table" then
|
||||
if not indexed_tables[v] then
|
||||
indexed_tables[v] = true
|
||||
new_table[i] = table.fill(tbl[i], replacement[i], false, indexed_tables)
|
||||
end
|
||||
elseif type(replacement[i]) == "table" then
|
||||
new_table[i] = table.deep_copy(replacement[i])
|
||||
else
|
||||
new_table[i] = replacement[i]
|
||||
end
|
||||
else
|
||||
new_table[i] = replacement[i]
|
||||
end
|
||||
end
|
||||
return new_table
|
||||
end
|
||||
--fill "holes" in the tables.
|
||||
function table.fill_in(tbl, replacement, preserve_reference, indexed_tables)
|
||||
if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing
|
||||
local new_table = tbl
|
||||
if not preserve_reference then
|
||||
new_table = table.deep_copy(tbl)
|
||||
end
|
||||
for i, v in pairs(replacement) do
|
||||
if new_table[i]==nil then
|
||||
if type(v)=="table" then
|
||||
new_table[i] = table.deep_copy(v)
|
||||
else
|
||||
new_table[i] = v
|
||||
end
|
||||
else
|
||||
if (type(new_table[i]) == "table") and (type(v) == "table") then
|
||||
table.fill_in(new_table[i], v, true, indexed_tables)
|
||||
end
|
||||
end
|
||||
end
|
||||
return new_table
|
||||
end
|
||||
--for class based OOP, ensure values containing a table in btbl are tables in a_tbl- instantiate, but do not fill.
|
||||
function table.instantiate_struct(tbl, btbl, indexed_tables)
|
||||
if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing
|
||||
for i, v in pairs(btbl) do
|
||||
if type(v) == "table" and not indexed_tables[v] then
|
||||
indexed_tables[v] = true
|
||||
if not tbl[i] then
|
||||
tbl[i] = table.instantiate_struct({}, v, indexed_tables)
|
||||
elseif type(tbl[i]) == "table" then
|
||||
tbl[i] = table.instantiate_struct(tbl[i], v, indexed_tables)
|
||||
end
|
||||
end
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
function table.shallow_copy(t)
|
||||
local new_table = {}
|
||||
for i, v in pairs(t) do
|
||||
new_table[i] = v
|
||||
end
|
||||
return new_table
|
||||
end
|
0
model_api.lua
Normal file
0
model_api.lua
Normal file
BIN
models/guns3d_character.b3d
Normal file
BIN
models/guns3d_character.b3d
Normal file
Binary file not shown.
BIN
models/guns3d_character.blend
Normal file
BIN
models/guns3d_character.blend
Normal file
Binary file not shown.
BIN
textures/gun_mrkr.png
Normal file
BIN
textures/gun_mrkr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 494 B |
BIN
textures/scope_back.png
Normal file
BIN
textures/scope_back.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
textures/scope_fore.png
Normal file
BIN
textures/scope_fore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
BIN
textures/smoke.png
Normal file
BIN
textures/smoke.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 285 B |
78
visual_effects.lua
Normal file
78
visual_effects.lua
Normal file
@ -0,0 +1,78 @@
|
||||
--designed for use with the gun class
|
||||
function Guns4d.muzzle_flash(self)
|
||||
local playername = self.player:get_player_name()
|
||||
if self.particle_spawners.muzzle_smoke and self.particle_spawners.muzzle_smoke ~= -1 then
|
||||
minetest.delete_particlespawner(self.particle_spawners.muzzle_smoke, self.player:get_player_name())
|
||||
end
|
||||
local dir, offset_pos = self:get_dir(), self:get_pos(self.properties.flash_offset)
|
||||
offset_pos=offset_pos+self.player:get_pos()
|
||||
local min = vector.rotate(vector.new(-2, -2, -.3), vector.dir_to_rotation(dir))
|
||||
local max = vector.rotate(vector.new(2, 2, .3), vector.dir_to_rotation(dir))
|
||||
minetest.add_particlespawner({
|
||||
exptime = .18,
|
||||
time = .1,
|
||||
amount = 15,
|
||||
attached = self.entity,
|
||||
pos = self.properties.flash_offset,
|
||||
radius = .04,
|
||||
glow = 3.5,
|
||||
vel = {min=vector.new(-1, -1, -.15), max=vector.new(1, 1, .15), bias=0},
|
||||
texpool = {
|
||||
{ name = "smoke.png", alpha_tween = {.25, 0}, scale = 2, blend = "alpha",
|
||||
animation = {
|
||||
type = "vertical_frames", aspect_w = 16,
|
||||
aspect_h = 16, length = .1,
|
||||
},
|
||||
},
|
||||
{ name = "smoke.png", alpha_tween = {.25, 0}, scale = .8, blend = "alpha",
|
||||
animation = {
|
||||
type = "vertical_frames", aspect_w = 16,
|
||||
aspect_h = 16, length = .1,
|
||||
},
|
||||
},
|
||||
{ name = "smoke.png^[multiply:#dedede", alpha_tween = {.25, 0}, scale = 2,
|
||||
blend = "alpha",
|
||||
animation = {
|
||||
type = "vertical_frames", aspect_h = 16,
|
||||
aspect_w = 16, length = .1,
|
||||
},
|
||||
},
|
||||
{ name = "smoke.png^[multiply:#b0b0b0", alpha_tween = {.2, 0}, scale = 2, blend = "alpha",
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = .25,
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
--muzzle smoke
|
||||
self.particle_spawners.muzzle_smoke = minetest.add_particlespawner({
|
||||
exptime = .3,
|
||||
time = 2,
|
||||
amount = 50,
|
||||
pos = self.properties.flash_offset,
|
||||
glow = 2,
|
||||
vel = {min=vector.new(-.1,.4,.2), max=vector.new(.1,.6,1), bias=0},
|
||||
attached = self.entity,
|
||||
texpool = {
|
||||
{name = "smoke.png", alpha_tween = {.12, 0}, scale = 1.4, blend = "alpha",
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = .35,
|
||||
},
|
||||
},
|
||||
{name = "smoke.png^[multiply:#b0b0b0", alpha_tween = {.2, 0}, scale = 1.4, blend = "alpha",
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = .35,
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user