diff --git a/block_values.lua b/block_values.lua index f390dfa..b23f37f 100644 --- a/block_values.lua +++ b/block_values.lua @@ -17,20 +17,18 @@ Guns4d.node_properties = {} --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() + print(table.tostring(minetest.registered_nodes["stairs:slab_wood"].groups)) + print(table.tostring(minetest.registered_nodes["default:wood"].groups)) 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*.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*.5 + RHA = RHA/(5*groups.choppy) end if groups.flora or groups.grass then RHA = 0 diff --git a/classes/Bullet_hole.lua b/classes/Bullet_hole.lua new file mode 100644 index 0000000..86622b1 --- /dev/null +++ b/classes/Bullet_hole.lua @@ -0,0 +1,64 @@ +local player_positions = {} +minetest.register_globalstep(function(dt) + +end) + +Bullet_hole = Instantiatable_class:inherit({ + unrendered_exptime = 20, + unrendered_texture = 'bullet_hole.png', + expiration_time = 60, + heat_effect = false, + render_distance = 50, + deletion_distance = 80, + timer = 0, + construct = function(def) + assert(def.pos) + end +}) +function Bullet_hole:render() + if self.old_timer then + --acount for the time lost. + self.timer = self.old_timer-(self.unrendered_exptime-self.timer) + end +end +function Bullet_hole:unrender() + self.old_timer = self.timer + self.timer = self.unrendered_exptime + minetest.add_particlespawner({ + pos = self.pos, + amount = 1, + time=0, + exptime = self.unrendered_exptime, + texture = { + name = 'bullet_hole.png', + alpha_tween = {1,0} + } + }) + if self.entity:get_pos() then + self.entity:remove() + end +end +function Bullet_hole:update() +end +function Bullet_hole:update_ent() +end +minetest.register_entity("guns4d:bullet_hole", { + initial_properties = { + visual = "cube", + visual_size = {x=.15, y=.15, z=0}, + pointable = false, + static_save = false, + use_texture_alpha = true, + textures = {"blank.png", "blank.png", "blank.png", "blank.png", "bullet_hole.png", "blank.png"} + }, + on_step = function(self, dtime) + if TICK % 50 then + local class_inst = self.class_Inst + if class_inst.timer < 30 then + local properties = self.object:get_properties() + properties.textures[5] = 'bullet_hole.png^[opacity:'..(math.floor((12.75*tostring(self.timer/30)))*20) + self.object:set_properties(properties) + end + end + end +}) \ No newline at end of file diff --git a/classes/Bullet_ray.lua b/classes/Bullet_ray.lua index 9c8a937..bc54dc3 100644 --- a/classes/Bullet_ray.lua +++ b/classes/Bullet_ray.lua @@ -3,6 +3,7 @@ local ray = { state = "free", --pos = pos, last_node = "", + hole_entity = "guns4d:bullet_hole", normal = vector.new(), --last_dir --exit_direction = dir, @@ -19,18 +20,17 @@ function ray:record_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:find_transverse_end_point() +function ray:find_transverse_edge() 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 + local cast1 = minetest.raycast(self.pos+(self.dir*(self.ITERATION_DISTANCE+.001)), self.pos, false, false) + for hit in cast1 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 + if hit.type == "node" and (vector.distance(hit.intersection_point, self.pos) > 0.0001) and (vector.equals(hit.under, self.last_pointed_node.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 + if (pointed) and (vector.distance(pointed.intersection_point, self.pos) < self.ITERATION_DISTANCE) then return pointed.intersection_point, pointed.intersection_normal end end @@ -42,12 +42,11 @@ function ray:_cast() local end_pos local edge - --if block ends early, then we find it and set end position of the ray accordingly. - --edge is where the section of solid blocks ends and becomes open air again. + --detect the "edge" of the block if self.state == "transverse" then - edge, end_normal = self:find_transverse_end_point() + edge, end_normal = self:find_transverse_edge() if edge then - end_pos = edge + end_pos = edge+(self.dir*.001) --give it a tolerance, it still needs to intersect with any node edges connected to the edge's block. next_state = "free" else end_pos = self.pos+(self.dir*self.ITERATION_DISTANCE) @@ -58,23 +57,33 @@ function ray:_cast() --do the main raycast. We don't account for mmRHA dropoff here. local continue = true --indicates wether to :_iterate wether the Bullet_ray has ended local cast = minetest.raycast(self.pos, end_pos, true, true) - local pointed + local edge_length + if edge then + edge_length = vector.distance(edge, self.pos) + end + local pointed_node + local pointed_object for hit in cast do - if vector.distance(hit.intersection_point, self.pos) > 0.0005 and vector.distance(hit.intersection_point, self.pos) < self.range then + local h_length = vector.distance(hit.intersection_point, self.pos) + if ( (not hit.ref) and h_length > 0.0001) and h_length < 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 + pointed_node = hit end_normal = hit.intersection_normal - end_pos = pointed.intersection_point + end_pos = pointed_node.intersection_point 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 + pointed_node = hit if minetest.get_node(hit.under).name ~= self.last_node_name then - pointed = hit - end_pos = pointed.intersection_point + end_pos = pointed_node.intersection_point + elseif edge then + if h_length-edge_length < 0.01 then + next_state = "transverse" + end 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 @@ -87,77 +96,66 @@ function ray:_cast() 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) and ((not self.last_pointed) or (hit.ref ~= self.last_pointed.ref)) then + if (hit.type == "object") and (hit.ref ~= self.player) and ((not self.last_pointed_object) or (hit.ref ~= self.last_pointed_object.ref)) then + minetest.chat_send_all("ent hit, ray") + end_pos = pointed_object.intersection_point if self.over_penetrate then - pointed = hit + pointed_object = hit break else - pointed = hit + pointed_object = hit continue = false break end - end_pos = pointed.intersection_point end end end - --[[if pointed then - end_pos = pointed.intersection_point - if self.state == "transverse" then - next_penetration_val = self.energy-(vector.distance(self.pos, end_pos)*Guns4d.node_properties[self.last_node_name].mmRHA) - else -- transverse - next_penetration_val = self.energy-(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.energy-(vector.distance(self.pos, end_pos)*Guns4d.node_properties[self.last_node_name].mmRHA) - else --free - continue = false - next_penetration_val = self.energy-(self.range*self.dropoff_mmRHA) - end - end]] - --set "last" values. - return pointed, next_state, end_pos, end_normal, continue + return pointed_node, pointed_object, next_state, end_pos, end_normal, continue end --the main function. function ray:_iterate(initialized) assert(self.instance, "attempt to call obj method on a class") - local pointed, next_state, end_pos, end_normal, continue = self:_cast() + local pointed_node, pointed_object, next_state, end_pos, end_normal, continue = self:_cast() local distance = vector.distance(self.pos, end_pos) if self.state == "free" then self.energy = self.energy-(distance*self.energy_dropoff) + if distance ~= self.pos+(self.dir*self.range) then + self:bullet_hole(end_pos, end_normal) + end else + if self.history[#self.history].state == "free" then + self:bullet_hole(self.pos, self.history[#self.history-1].normal) + end + if next_state == "free" then + self:bullet_hole(end_pos, end_normal) + end local penetration_loss = distance*Guns4d.node_properties[self.last_node_name].mmRHA --calculate our energy loss based on the percentage of energy our penetration represents. - minetest.chat_send_all(penetration_loss/self.init_penetration) - minetest.chat_send_all(distance) - minetest.chat_send_all(Guns4d.node_properties[self.last_node_name].mmRHA) - --minetest.chat_send_all(penetration_loss) self.energy = self.energy-((self.init_energy*self.energy_sharp_ratio)*(penetration_loss/self.init_penetration)) + end + if self.state ~= self.next_state then + end --set values for next iteration. self.range = self.range-distance if self.range <= 0.0005 or self.energy < 0 then continue = false - minetest.chat_send_all("range ended, dist:"); minetest.chat_send_all(tostring(distance)) end ---@diagnostic disable-next-line: assign-type-mismatch self.state = next_state - if pointed then - self.last_pointed = pointed - self.pos = pointed.intersection_point - if self.energy > 0 then - if pointed.type == "node" then - self.last_node_name = minetest.get_node(pointed.under).name - elseif pointed.type == "object" then - ray:hit_entity(pointed.ref) - end - end + if pointed_object then + self.pos = pointed_object.intersection_point + self.last_pointed_object = pointed_object + ray:hit_entity(pointed_object.ref) else self.pos = end_pos end + if pointed_node then + self.last_node_name = minetest.get_node(pointed_node.under).name + self.last_pointed_node = pointed_node + end table.insert(self.history, { pos = self.pos, energy = self.energy, @@ -172,7 +170,7 @@ function ray:_iterate(initialized) for i, v in pairs(self.history) do local hud = self.player:hud_add({ hud_elem_type = "waypoint", - text = "mmRHA:"..tostring(v.energy).." ", + text = " "..self.history[i].energy, number = 255255255, precision = 1, world_pos = v.pos, @@ -180,21 +178,40 @@ function ray:_iterate(initialized) alignment = {x=0,y=0}, offset = {x=0,y=0}, }) - minetest.after(40, function(hud) + minetest.after(15, function(hud) self.player:hud_remove(hud) end, hud) end end end -function ray:calculate_blunt_damage(bullet, armor, groups) -end function ray:calculate_sharp_conversion(bullet, armor, groups) end function ray:calculate_sharp_damage(bullet, armor, groups) end +function ray:calculate_blunt_damage(bullet, armor, groups) +end function ray:apply_damage(object, blunt_pen, sharp_pen, blunt_dmg, sharp_dmg) + minetest.chat_send_all("ent hit") object:punch() end +function ray:bullet_hole(pos, normal) + local nearby_players = false + for pname, player in pairs(minetest.get_connected_players()) do + if vector.distance(player:get_pos(), pos) < 50 then + nearby_players = true; break + end + end + --if it's close enough to any players, then add it + if nearby_players then + --this entity will keep track of itself. + local ent = minetest.add_entity(pos+(normal*(.0001+math.random()/1000)), self.hole_entity) + ent:set_rotation(vector.dir_to_rotation(normal)) + local lua_ent = ent:get_luaentity() + lua_ent.block_pos = pos + else + Guns4d.effects.spawn_bullet_hole_particle(pos, self.hole_scale, '(bullet_hole_1.png^(bullet_hole_2.png^[opacity:129))') + end +end function ray.construct(def) if def.instance then assert(def.player, "no player") diff --git a/classes/Gun.lua b/classes/Gun.lua index d5d7a77..ed4e4d1 100644 --- a/classes/Gun.lua +++ b/classes/Gun.lua @@ -141,7 +141,7 @@ local gun_default = { time_since_last_fire = 0, time_since_creation = 0, rechamber_time = 0, - muzzle_flash = Guns4d.muzzle_flash + muzzle_flash = Guns4d.effects.muzzle_flash } function gun_default:attempt_fire() @@ -290,7 +290,7 @@ 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() end + if not self:has_entity() then self:add_entity(); self:clear_animation() end self.pos = self:get_pos() local handler = self.handler local look_rotation = {x=handler.look_rotation.x,y=handler.look_rotation.y} @@ -448,9 +448,6 @@ function gun_default:set_animation(frames, length, fps, loop) end function gun_default:clear_animation() local loaded = false - for i, v in pairs(self.ammo_handler) do - print(i,v ) - end if self.properties.ammo.magazine_only then if self.ammo_handler.ammo.loaded_mag ~= "empty" then loaded = true @@ -612,7 +609,6 @@ gun_default.construct = function(def) --fill in the properties. def.properties = table.fill(def.parent_class.properties, props or {}) - print(table.tostring(def.properties)) def.consts = table.fill(def.parent_class.consts, def.consts or {}) props = def.properties --have to reinitialize this as the reference is replaced. diff --git a/classes/Instantiatable_class.lua b/classes/Instantiatable_class.lua index 9370245..9e23240 100644 --- a/classes/Instantiatable_class.lua +++ b/classes/Instantiatable_class.lua @@ -2,7 +2,6 @@ Instantiatable_class = { instance = false, __no_copy = true } ---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 diff --git a/model_reader.lua b/model_reader.lua new file mode 100644 index 0000000..e69de29 diff --git a/patches/3d_armor.lua b/patches/3d_armor.lua new file mode 100644 index 0000000..e69de29 diff --git a/textures/bullet_hole.png b/textures/bullet_hole.png new file mode 100644 index 0000000..a676b28 Binary files /dev/null and b/textures/bullet_hole.png differ diff --git a/textures/bullet_hole_1.png b/textures/bullet_hole_1.png new file mode 100644 index 0000000..bbfb350 Binary files /dev/null and b/textures/bullet_hole_1.png differ diff --git a/textures/bullet_hole_2.png b/textures/bullet_hole_2.png new file mode 100644 index 0000000..f2d66d6 Binary files /dev/null and b/textures/bullet_hole_2.png differ diff --git a/visual_effects.lua b/visual_effects.lua index ccbec93..8e1b646 100644 --- a/visual_effects.lua +++ b/visual_effects.lua @@ -1,5 +1,9 @@ +Guns4d.effects={ + bullet_holes = {} +} + --designed for use with the gun class -function Guns4d.muzzle_flash(self) +function Guns4d.effects.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()) @@ -66,13 +70,90 @@ function Guns4d.muzzle_flash(self) }, }, {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, - }, + animation = {type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = .35,}, } } }) -end \ No newline at end of file +end +function Guns4d.effects.spawn_bullet_hole_particle(pos, size, texture) + --modern syntax isn't accepted by add particle to my knowledge, or it's not documented. + --so I have to use a particle spawner + minetest.add_particlespawner({ + pos = pos, + amount = 1, + time=.1, + exptime = 10, + texture = { + name = 'bullet_hole.png', + alpha_tween = {1,0} + } + }) +end +local bullet_holes = Guns4d.effects.bullet_holes +local hole_despawn_dist = 20 +local time_since_last_check = 5 +minetest.register_globalstep(function(dt) + if time_since_last_check >= 5 then + time_since_last_check = 0 + for i, v in pairs(bullet_holes) do + local pos = v:get_pos() + if pos then + local nearby_players = false + for pname, player in pairs(minetest.get_connected_players()) do + if vector.distance(player:get_pos(), pos) < hole_despawn_dist then + nearby_players = true + end + end + if not nearby_players then + local props = v:get_properties() + Guns4d.effects.spawn_bullet_hole_particle(v:get_pos(), props.visual_size.x, props.textures[5]) + bullet_holes[i]:remove() + table.remove(bullet_holes, i) + end + else + --if pos is nil, we know the bullet delete itself. + table.remove(bullet_holes, i) + end + end + else + time_since_last_check = time_since_last_check + dt + end +end) +minetest.register_entity("guns4d:bullet_hole", { + initial_properties = { + visual = "cube", + visual_size = {x=.15, y=.15, z=0}, + pointable = false, + static_save = false, + use_texture_alpha = true, + textures = {"blank.png", "bullet_hole.png", "blank.png", "blank.png", "bullet_hole.png", "bullet_hole.png"} + }, + on_step = function(self, dtime) + if not self.block_name then + table.insert(bullet_holes, 1, self.object) + self.block_name = minetest.get_node(self.block_pos).name + elseif (TICK%3==0) and (self.block_name ~= minetest.get_node(self.block_pos).name) then + self.object:remove() + return + end + + if not self.timer then + local properties = self.object:get_properties() + self.timer = 31 + properties.textures[5] = 'bullet_hole.png' + self.object:set_properties(properties) + else + self.timer = self.timer - dtime + end + if self.timer < 30 then + if self.timer < 0 then + self.object:remove() + minetest.chat_send_all("removed") + return + end + local properties = self.object:get_properties() + properties.textures[5] = 'bullet_hole.png^[opacity:'..(math.floor((12.75*tostring(self.timer/30)))*20) + self.object:set_properties(properties) + end + end +}) \ No newline at end of file