diff --git a/shooter/api.lua b/shooter/api.lua index d82bb5b..512fb30 100644 --- a/shooter/api.lua +++ b/shooter/api.lua @@ -1,16 +1,9 @@ shooter = { - time = 0, - objects = {}, - rounds = {}, - shots = {}, - update_time = 0, - reload_time = 0, - player_offset = {x=0, y=1, z=0}, - entity_offset = {x=0, y=0, z=0}, + registered_weapons = {}, } shooter.config = { - admin_weapons = false, + admin_weapons = true, enable_blasting = false, enable_particle_fx = true, enable_protection = false, @@ -19,25 +12,66 @@ shooter.config = { allow_nodes = true, allow_entities = false, allow_players = true, - object_reload_time = 1, - object_update_time = 0.25, rounds_update_time = 0.4, + camera_height = 1.5, } +local rounds = {} +local shots = {} +local shooting = {} local config = shooter.config -local singleplayer = minetest.is_singleplayer() -local allowed_entities = {} +local server_step = minetest.settings:get("dedicated_server_step") -local function get_dot_product(v1, v2) - return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z -end - -local function get_particle_pos(p, v, d) - return vector.add(p, vector.multiply(v, {x=d, y=d, z=d})) -end - -function shooter:set_shootable_entity(name) - allowed_entities[name] = 1 +function shooter:register_weapon(name, def) + shooter.registered_weapons[name] = def + -- Fix definition table + def.sounds = def.sounds or {} + def.sounds.reload = def.sounds.reload or "shooter_reload" + def.sounds.fail_shot = def.sounds.fail_shot or "shooter_click" + def.reload_item = def.reload_item or "shooter:ammo" + def.spec.tool_caps.full_punch_interval = math.max(server_step, + def.spec.tool_caps.full_punch_interval) + def.spec.wear = math.ceil(65535 / def.spec.rounds) + def.spec.unloaded_item = name + def.unloaded_item = def.unloaded_item or { + description = def.description.." (unloaded)", + inventory_image = def.inventory_image, + } + -- Register loaded item tool + minetest.register_tool(name.."_loaded", { + description = def.description, + inventory_image = def.inventory_image, + on_use = function(itemstack, user) + if shooter:fire_weapon(user, itemstack, def.spec) then + itemstack:add_wear(def.spec.wear) + if itemstack:get_count() == 0 then + itemstack = def.unloaded_item.name + end + end + return itemstack + end, + groups = {not_in_creative_inventory=1}, + }) + -- Register unloaded item tool + minetest.register_tool(name, { + description = def.unloaded_item.description, + inventory_image = def.unloaded_item.inventory_image, + groups = def.unloaded_item.groups or {}, + on_use = function(itemstack, user, pointed_thing) + local inv = user:get_inventory() + if inv then + local stack = def.reload_item + if inv:contains_item("main", stack) then + minetest.sound_play((def.sounds.reload), {object=user}) + inv:remove_item("main", stack) + itemstack:replace(name.."_loaded 1 1") + else + minetest.sound_play((def.sounds.fail_shot), {object=user}) + end + end + return itemstack + end, + }) end function shooter:spawn_particles(pos, texture) @@ -68,7 +102,7 @@ function shooter:play_node_sound(node, pos) end end -function shooter:punch_node(pos, def) +function shooter:punch_node(pos, spec) local node = minetest.get_node(pos) if not node then return @@ -78,12 +112,12 @@ function shooter:punch_node(pos, def) return end if config.enable_protection then - if minetest.is_protected(pos, def.name) then + if minetest.is_protected(pos, spec.user) then return end end if item.groups then - for k, v in pairs(def.groups) do + for k, v in pairs(spec.groups) do local level = item.groups[k] or 0 if level >= v then minetest.remove_node(pos) @@ -107,264 +141,105 @@ function shooter:is_valid_object(object) if config.allow_entities == true then local luaentity = object:get_luaentity() if luaentity then - if luaentity.name then - if allowed_entities[luaentity.name] then - return true - end - end + return luaentity.name ~= "__builtin:item" end end end end -function shooter:get_intersect_pos(ray, plane, collisionbox) - local v = vector.subtract(ray.pos, plane.pos) - local r1 = get_dot_product(v, plane.normal) - local r2 = get_dot_product(ray.dir, plane.normal) - if r2 ~= 0 then - local t = -(r1 / r2) - local td = vector.multiply(ray.dir, {x=t, y=t, z=t}) - local pt = vector.add(ray.pos, td) - local pd = vector.subtract(pt, plane.pos) - if math.abs(pd.x) < collisionbox[4] and - math.abs(pd.y) < collisionbox[5] and - math.abs(pd.z) < collisionbox[6] then - return pt - end - end -end - -function shooter:process_round(round) - local target = {object=nil, distance=10000} - local p1 = round.pos - local v1 = round.ray - for _,ref in ipairs(shooter.objects) do - local p2 = vector.add(ref.pos, ref.offset) - if p1 and p2 and ref.name ~= round.name then - local d = vector.distance(p1, p2) - if d < round.def.step and d < target.distance then - local ray = {pos=p1, dir=v1} - local plane = {pos=p2, normal={x=-1, y=0, z=-1}} - local pos = shooter:get_intersect_pos(ray, plane, ref.collisionbox) - if pos then - target.object = ref.object - target.pos = pos - target.distance = d - end - end - end - end - if target.object and target.pos then - local success, pos = minetest.line_of_sight(p1, target.pos, 1) - if success then - local user = minetest.get_player_by_name(round.name) - if user then - target.object:punch(user, nil, round.def.tool_caps, v1) - shooter:spawn_particles(target.pos, config.explosion_texture) - end - return 1 - elseif pos and config.allow_nodes == true then - shooter:punch_node(pos, round.def) - return 1 - end - elseif config.allow_nodes == true then - local d = round.def.step - local p2 = vector.add(p1, vector.multiply(v1, {x=d, y=d, z=d})) - local success, pos = minetest.line_of_sight(p1, p2, 1) - if pos then - shooter:punch_node(pos, round.def) - return 1 - end - end -end - -shooter.registered_weapons = shooter.registered_weapons or {} -function shooter:register_weapon(name, def) - shooter.registered_weapons[name] = def - local shots = def.shots or 1 - local wear = math.ceil(65534 / def.rounds) - local max_wear = (def.rounds - 1) * wear - -- Fix sounds table - def.sounds = def.sounds or {} - -- Default sounds - def.sounds.reload = def.sounds.reload or "shooter_reload" - def.sounds.fail_shot = def.sounds.fail_shot or "shooter_click" - -- Assert reload item - def.reload_item = def.reload_item or "shooter:ammo" - minetest.register_tool(name, { - description = def.description, - inventory_image = def.inventory_image, - on_use = function(itemstack, user, pointed_thing) - if itemstack:get_wear() < max_wear then - def.spec.name = user:get_player_name() - if shots > 1 then - local step = def.spec.tool_caps.full_punch_interval - for i = 0, step * shots, step do - minetest.after(i, function() - shooter:fire_weapon(user, pointed_thing, def.spec) - end) - end - else - shooter:fire_weapon(user, pointed_thing, def.spec) - end - itemstack:add_wear(wear) - else - local inv = user:get_inventory() - if inv then - local stack = def.reload_item .. " 1" - if inv:contains_item("main", stack) then - minetest.sound_play((def.sounds.reload), {object=user}) - inv:remove_item("main", stack) - if def.unloaded_item then - itemstack:replace(def.unloaded_item.name.." 1 1") - else - itemstack:replace(name.." 1 1") - end - else - minetest.sound_play((def.sounds.fail_shot), {object=user}) - end - end - end - -- Replace to unloaded item - if def.unloaded_item and (itemstack:get_wear() + wear) > 65534 then - itemstack:set_name(def.unloaded_item.name) - end - return itemstack - end, - }) - -- Register unloaded item tool - if def.unloaded_item then - local groups = {} - if def.unloaded_item.not_in_creative_inventory == true then - groups = {not_in_creative_inventory=1} - end - minetest.register_tool(def.unloaded_item.name, { - description = def.unloaded_item.description, - inventory_image = def.unloaded_item.inventory_image, - groups = groups, - on_use = function(itemstack, user, pointed_thing) - local inv = user:get_inventory() - if inv then - local stack = def.reload_item .. " 1" - if inv:contains_item("main", stack) then - minetest.sound_play((def.sounds.reload), {object=user}) - inv:remove_item("main", stack) - itemstack:replace(name.." 1 1") - else - minetest.sound_play((def.sounds.fail_shot), {object=user}) - end - end - return itemstack - end, - }) - end -end - -function shooter:fire_weapon(user, pointed_thing, def) - if shooter.shots[def.name] then - if shooter.time < shooter.shots[def.name] then - return - end - end - shooter.shots[def.name] = shooter.time + def.tool_caps.full_punch_interval - minetest.sound_play(def.sound, {object=user}) - local v1 = user:get_look_dir() - local p1 = user:getpos() - if not v1 or not p1 then +local function process_round(round) + round.dist = round.dist + round.spec.step + if round.dist > round.spec.range then return end - minetest.add_particle({x=p1.x, y=p1.y + 1.6, z=p1.z}, - vector.multiply(v1, {x=30, y=30, z=30}), - {x=0, y=0, z=0}, 0.5, 0.25, - false, def.particle - ) - if pointed_thing.type == "node" and config.allow_nodes == true then - local pos = minetest.get_pointed_thing_position(pointed_thing, false) - shooter:punch_node(pos, def) + local p1 = round.pos + local p2 = vector.add(p1, vector.multiply(round.dir, round.spec.step)) + local ray = minetest.raycast(p1, p2, true, true) + local pointed_thing = ray:next() or {} + if pointed_thing.type == "node" then + if config.allow_nodes == true then + local pos = minetest.get_pointed_thing_position(pointed_thing, false) + shooter:punch_node(pos, round.spec) + end + return elseif pointed_thing.type == "object" then local object = pointed_thing.ref if shooter:is_valid_object(object) == true then - object:punch(user, nil, def.tool_caps, v1) - local p2 = object:getpos() - local pp = get_particle_pos(p1, v1, vector.distance(p1, p2)) - pp.y = pp.y + 1.75 - shooter:spawn_particles(pp, config.explosion_texture) + local player = minetest.get_player_by_name(round.spec.user) + if player then + object:punch(player, nil, round.spec.tool_caps, round.dir) + local pos = pointed_thing.intersection_point or object:get_pos() + shooter:spawn_particles(pos, config.explosion_texture) + end end - else - shooter:update_objects() - table.insert(shooter.rounds, { - name = def.name, - pos = vector.add(p1, {x=0, y=1.75, z=0}), - ray = v1, - dist = 0, - def = def, - }) + return end + round.pos = p2 + minetest.after(shooter.config.rounds_update_time, function(round) + process_round(round) + end, round) end -function shooter:load_objects() - local objects = {} - if config.allow_players == true then - local players = minetest.get_connected_players() - for _,player in ipairs(players) do - local pos = player:getpos() - local name = player:get_player_name() - local hp = player:get_hp() or 0 - if pos and name and hp > 0 then - table.insert(objects, { - name = name, - object = player, - pos = pos, - collisionbox = {-0.25,-1.0,-0.25, 0.25,0.8,0.25}, - offset = shooter.player_offset, - }) - end +local function fire_weapon(player, itemstack, spec, extended) + if not player then + return + end + local dir = player:get_look_dir() + local pos = player:get_pos() + if not dir or not pos then + return + end + pos.y = pos.y + config.camera_height + minetest.sound_play(spec.sound, {object=player}) + minetest.add_particle({ + pos = pos, + velocity = vector.multiply(dir, 30), + acceleration = {x=0, y=0, z=0}, + expirationtime = 0.5, + size = 0.25, + texture = spec.particle, + }) + process_round({ + spec = spec, + pos = vector.add(pos, dir), + dir = dir, + dist = 0, + }) + if extended then + itemstack:add_wear(spec.wear) + if itemstack:get_count() == 0 then + itemstack = spec.unloaded_item + player:set_wielded_item(itemstack) + return end + player:set_wielded_item(itemstack) end - if config.allow_entities == true then - for _,ref in pairs(minetest.luaentities) do - if ref.object and ref.name then - if allowed_entities[ref.name] then - local pos = ref.object:getpos() - local hp = ref.object:get_hp() or 0 - if pos and hp > 0 then - local def = minetest.registered_entities[ref.name] - table.insert(objects, { - name = ref.name, - object = ref.object, - pos = pos, - collisionbox = def.collisionbox or {0,0,0, 0,0,0}, - offset = shooter.entity_offset, - }) - end - end - end + if not spec.automatic or not shooting[spec.user] then + return + end + local interval = spec.tool_caps.full_punch_interval + shots[spec.user] = minetest.get_us_time() / 1000000 + interval + minetest.after(interval, function(player, itemstack, spec) + if shooting[spec.user] then + fire_weapon(player, itemstack, spec, true) end - end - shooter.reload_time = shooter.time - shooter.update_time = shooter.time - shooter.objects = {} - for _,v in ipairs(objects) do - table.insert(shooter.objects, v) - end + end, player, itemstack, spec) end -function shooter:update_objects() - if shooter.time - shooter.reload_time > config.object_reload_time then - shooter:load_objects() - elseif shooter.time - shooter.update_time > config.object_update_time then - for i, ref in ipairs(shooter.objects) do - if ref.object then - local pos = ref.object:getpos() - if pos then - shooter.objects[i].pos = pos - end - else - table.remove(shooter.objects, i) - end - end - shooter.update_time = shooter.time +function shooter:fire_weapon(player, itemstack, spec) + local name = player:get_player_name() + local time = minetest.get_us_time() / 1000000 + if shots[name] and time <= shots[name] then + return false end + if config.admin_weapons and minetest.check_player_privs(name, + {server=true}) then + spec.automatic = true + end + shooting[name] = true + spec.user = name + fire_weapon(player, itemstack, spec) + return true end function shooter:blast(pos, radius, fleshy, distance, user) @@ -394,14 +269,12 @@ function shooter:blast(pos, radius, fleshy, distance, user) end local objects = minetest.get_objects_inside_radius(pos, distance) for _,obj in ipairs(objects) do - if (obj:is_player() and config.allow_players == true) or - (obj:get_luaentity() and config.allow_entities == true and - obj:get_luaentity().name ~= "__builtin:item") then + if shooter:is_valid_object(obj) then local obj_pos = obj:getpos() local dist = vector.distance(obj_pos, pos) local damage = (fleshy * 0.5 ^ dist) * 2 if dist ~= 0 then - obj_pos.y = obj_pos.y + 1.7 + obj_pos.y = obj_pos.y + 1 blast_pos = {x=pos.x, y=pos.y + 4, z=pos.z} if minetest.line_of_sight(obj_pos, blast_pos, 1) then obj:punch(user, 1.0, { @@ -445,32 +318,11 @@ function shooter:blast(pos, radius, fleshy, distance, user) end end -if not singleplayer and config.admin_weapons then - local timer = 0 - local shooting = false - minetest.register_globalstep(function(dtime) - if not shooting then - timer = timer+dtime - if timer < 2 then - return - end - timer = 0 +minetest.register_globalstep(function(dtime) + for _,player in pairs(minetest.get_connected_players()) do + local name = player:get_player_name() + if name then + shooting[name] = player:get_player_control().LMB == true end - shooting = false - for _,player in pairs(minetest.get_connected_players()) do - if player:get_player_control().LMB then - local name = player:get_player_name() - if minetest.check_player_privs(name, {server=true}) then - local spec = shooter.registered_weapons[player:get_wielded_item():get_name()] - if spec then - spec = spec.spec - shooter.shots[name] = false - spec.name = name - shooter:fire_weapon(player, {}, spec) - shooting = true - end - end - end - end - end) -end + end +end) diff --git a/shooter/init.lua b/shooter/init.lua index 59ca8bb..136bd32 100644 --- a/shooter/init.lua +++ b/shooter/init.lua @@ -30,25 +30,6 @@ for name, _ in pairs(shooter.config) do shooter.config[name] = _G[global] end end -if minetest.global_exists("SHOOTER_PLAYER_OFFSET") then - shooter.player_offset = SHOOTER_PLAYER_OFFSET -end -if minetest.global_exists("SHOOTER_ENTITY_OFFSET") then - shooter.entity_offset = SHOOTER_ENTITY_OFFSET -end -if minetest.global_exists("SHOOTER_ENTITIES") then - for _, name in pairs(SHOOTER_ENTITIES) do - shooter:set_shootable_entity(name) - end -end - --- Simple Mobs Support - -for name, _ in pairs(minetest.registered_entities) do - if string.find(name, "^mobs") then - shooter:set_shootable_entity(name) - end -end -- Load Configuration diff --git a/shooter_crossbow/init.lua b/shooter_crossbow/init.lua index 4500ccd..460e783 100644 --- a/shooter_crossbow/init.lua +++ b/shooter_crossbow/init.lua @@ -143,42 +143,27 @@ minetest.register_entity("shooter_crossbow:arrow_entity", { return end if self.timer > 0.2 then - local pos = self.object:getpos() local dir = vector.normalize(self.object:getvelocity()) local frame = get_animation_frame(dir) - self.object:set_animation({x=frame, y=frame}, 0) - local objects = minetest.get_objects_inside_radius(pos, 5) - for _,obj in ipairs(objects) do - if shooter:is_valid_object(obj) and obj ~= self.player then - local collisionbox = {-0.25,-1.0,-0.25, 0.25,0.8,0.25} - local offset = shooter.player_offset - if not obj:is_player() then - offset = shooter.entity_offset - local ent = obj:get_luaentity() - if ent then - local def = minetest.registered_entities[ent.name] - collisionbox = def.collisionbox or collisionbox - end - end - local opos = vector.add(obj:getpos(), offset) - local ray = {pos=pos, dir=dir} - local plane = {pos=opos, normal={x=-1, y=0, z=-1}} - local ipos = shooter:get_intersect_pos(ray, plane, collisionbox) - if ipos then - self:strike(obj) - end + local p1 = vector.add(self.object:getpos(), dir) + local p2 = vector.add(p1, vector.multiply(dir, 4)) + local ray = minetest.raycast(p1, p2, true, true) + local pointed_thing = ray:next() or {} + if pointed_thing.type == "object" then + local obj = pointed_thing.ref + if shooter:is_valid_object(obj) then + self:strike(obj) end - end - local p = vector.add(pos, vector.multiply(dir, {x=5, y=5, z=5})) - local _, npos = minetest.line_of_sight(pos, p, 1) - if npos then - local node = minetest.get_node(npos) - local tpos = get_target_pos(pos, npos, dir, 0.66) - self.node_pos = npos + elseif pointed_thing.type == "node" then + local pos = minetest.get_pointed_thing_position(pointed_thing, false) + local node = minetest.get_node(pos) + local target_pos = get_target_pos(p1, pos, dir, 0.66) + self.node_pos = pos self.state = "stuck" - self:stop(tpos) - shooter:play_node_sound(node, npos) + self:stop(target_pos) + shooter:play_node_sound(node, pos) end + self.object:set_animation({x=frame, y=frame}, 0) self.timer = 0 end end, @@ -206,7 +191,8 @@ for _, color in pairs(dye_basecolors) do local dir = user:get_look_dir() local yaw = user:get_look_yaw() if pos and dir and yaw then - pos.y = pos.y + 1.5 + pos.y = pos.y + shooter.config.camera_height + pos = vector.add(pos, dir) local obj = minetest.add_entity(pos, "shooter_crossbow:arrow_entity") local ent = nil if obj then @@ -310,7 +296,6 @@ if shooter.config.enable_crafting == true then end end - --Backwards compatibility minetest.register_alias("shooter:crossbow", "shooter_crossbow:crossbow") for _, color in pairs(dye_basecolors) do diff --git a/shooter_guns/init.lua b/shooter_guns/init.lua index 85d06c4..220783e 100644 --- a/shooter_guns/init.lua +++ b/shooter_guns/init.lua @@ -1,9 +1,9 @@ shooter:register_weapon("shooter_guns:pistol", { description = "Pistol", inventory_image = "shooter_pistol.png", - rounds = 200, spec = { - range = 100, + rounds = 200, + range = 160, step = 20, tool_caps = {full_punch_interval=0.5, damage_groups={fleshy=2}}, groups = {snappy=3, fleshy=3, oddly_breakable_by_hand=3}, @@ -15,9 +15,9 @@ shooter:register_weapon("shooter_guns:pistol", { shooter:register_weapon("shooter_guns:rifle", { description = "Rifle", inventory_image = "shooter_rifle.png", - rounds = 100, spec = { - range = 200, + rounds = 100, + range = 240, step = 30, tool_caps = {full_punch_interval=1.0, damage_groups={fleshy=3}}, groups = {snappy=3, crumbly=3, choppy=3, fleshy=2, oddly_breakable_by_hand=2}, @@ -29,9 +29,9 @@ shooter:register_weapon("shooter_guns:rifle", { shooter:register_weapon("shooter_guns:shotgun", { description = "Shotgun", inventory_image = "shooter_shotgun.png", - rounds = 50, spec = { - range = 50, + rounds = 50, + range = 60, step = 15, tool_caps = {full_punch_interval=1.5, damage_groups={fleshy=4}}, groups = {cracky=3, snappy=2, crumbly=2, choppy=2, fleshy=1, oddly_breakable_by_hand=1}, @@ -43,12 +43,12 @@ shooter:register_weapon("shooter_guns:shotgun", { shooter:register_weapon("shooter_guns:machine_gun", { description = "Sub Machine Gun", inventory_image = "shooter_smgun.png", - rounds = 50, - shots = 4, spec = { - range = 100, + automatic = true, + rounds = 100, + range = 160, step = 20, - tool_caps = {full_punch_interval=0.125, damage_groups={fleshy=2}}, + tool_caps = {full_punch_interval=0.1, damage_groups={fleshy=2}}, groups = {snappy=3, fleshy=3, oddly_breakable_by_hand=3}, sound = "shooter_pistol", particle = "shooter_cap.png", @@ -100,32 +100,6 @@ if shooter.config.enable_crafting == true then }) end -local rounds_update_time = 0 - -minetest.register_globalstep(function(dtime) - shooter.time = shooter.time + dtime - if shooter.time - rounds_update_time > shooter.config.rounds_update_time then - for i, round in ipairs(shooter.rounds) do - if shooter:process_round(round) or round.dist > round.def.range then - table.remove(shooter.rounds, i) - else - local v = vector.multiply(round.ray, round.def.step) - shooter.rounds[i].pos = vector.add(round.pos, v) - shooter.rounds[i].dist = round.dist + round.def.step - end - end - rounds_update_time = shooter.time - end - if shooter.time > 100000 then - shooter.shots = {} - rounds_update_time = 0 - shooter.reload_time = 0 - shooter.update_time = 0 - shooter.time = 0 - end -end) - - --Backwards compatibility minetest.register_alias("shooter:shotgun", "shooter_guns:shotgun") minetest.register_alias("shooter:pistol", "shooter_guns:pistol")