diff --git a/LICENSE.txt b/LICENSE.txt index 412765a..6b0af2d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -7,6 +7,9 @@ License Textures: Stuart Jones - WTFPL License Sounds: freesound.org - flobert1_20070728.wav by Nonoo - Attribution 3.0 Unported (CC BY 3.0) + flobert1_20070728.wav by Nonoo - Attribution 3.0 Unported (CC BY 3.0) + + shot.wav by Sergenious - Attribution 3.0 Unported (CC BY 3.0) + GUNSHOT.WAV by erkanozan - CC0 1.0 Universal (CC0 1.0) diff --git a/README.txt b/README.txt index f4988f1..b726015 100644 --- a/README.txt +++ b/README.txt @@ -1,38 +1,37 @@ Minetest Mod - Simple Shooter [shooter] ======================================= -Mod Version: 0.1.0 +Mod Version: 0.2.0 Minetest Version: 0.4.8 Depends: default -An experimental first person shooter mod using vector mathematics instead of -physical projectile entities. This has a number of advantages along with a -number disadvantages. +An experimental first person shooter mod using simple vector mathematics +in an effort to find a more server-firendly method of hit detection from +that which is currently being used by the firearms mod. -Pros: +For the most part I think I have achieved this for straight pvp, however, +the jury is still out as to whether it is any faster against entities (mobs) +One big downside of this method is that it only works against ordinary nodes +within pointable range of the player. -Fast and responsive -Fairly light weight -Not affected by chunk boundaries +By default this mod is configured to work only against other players in +multiplayer (server) mode. This is overridden in singleplayer mode to work +against all registered entities instead. -Cons: +Default configuration can be customised by adding a shooter.conf file to the +mod's main directory, see shooter.conf.example for more details. -Only works against other players -Slightly less realistic - -This is still very much a work in progress and currently not much use in a -singleplayer game. I plan to eventually use this as a base for a 'Spades' style -FPS game using the minetest engine, however, I decided to add a couple of craft -recipes and release this simple version for minetest_game. +This is still very much a work in progress which I plan to eventually use as +the base for a 'Spades' style FPS game using the minetest engine. Crafting ======== S = Steel Ingot [default:steel_ingot] B = Bronze Ingot [default:bronze_ingot] -M = Mese Crystal [default:mese_crystal] +M = Mese Crystal [default:mese_crysytal] Pistol: [shooter:pistol] @@ -52,3 +51,13 @@ Riffle: [shooter:riffle] | | M | B | +---+---+---+ +Shotgun: [shooter:shotgun] + ++---+---+---+ +| S | | | ++---+---+---+ +| | S | | ++---+---+---+ +| | M | B | ++---+---+---+ + diff --git a/init.lua b/init.lua index 54f5cf4..e10d161 100644 --- a/init.lua +++ b/init.lua @@ -1,33 +1,52 @@ dofile(minetest.get_modpath(minetest.get_current_modname()).."/shooter.lua") minetest.register_tool("shooter:pistol", { - description = "Pistol", - inventory_image = "shooter_pistol.png", + description = "Pistol", + inventory_image = "shooter_pistol.png", on_use = function(itemstack, user, pointed_thing) - shooter:fire_weapon(user, { - range_func = {a=-0.1, b=-1.5, c=100}, - tool_caps = {full_punch_interval=1.0, damage_groups={fleshy=3}}, + shooter:fire_weapon(user, pointed_thing, { + range = 30, + tool_caps = {full_punch_interval=0.5, damage_groups={fleshy=1}}, + groups = {snappy=3, oddly_breakable_by_hand=3}, sound = "shooter_pistol", + particle = "default_obsidian.png", }) itemstack:add_wear(328) -- 200 Rounds - return itemstack end, }) minetest.register_tool("shooter:riffle", { - description = "Riffle", - inventory_image = "shooter_riffle.png", + description = "Riffle", + inventory_image = "shooter_riffle.png", on_use = function(itemstack, user, pointed_thing) - shooter:fire_weapon(user, { - range_func = {a=-0.02, b=-0.6, c=100}, - tool_caps = {full_punch_interval=1.0, damage_groups={fleshy=5}}, + shooter:fire_weapon(user, pointed_thing, { + range = 80, + tool_caps = {full_punch_interval=1.0, damage_groups={fleshy=2}}, + groups = {snappy=3, crumbly=3, choppy=3, oddly_breakable_by_hand=2}, sound = "shooter_riffle", + particle = "default_gold_block.png", }) itemstack:add_wear(656) -- 100 Rounds return itemstack end, }) +minetest.register_tool("shooter:shotgun", { + description = "Shotgun", + inventory_image = "shooter_shotgun.png", + on_use = function(itemstack, user, pointed_thing) + shooter:fire_weapon(user, pointed_thing, { + range = 15, + tool_caps = {full_punch_interval=1.5, damage_groups={fleshy=4}}, + groups = {cracky=3, snappy=2, crumbly=2, choppy=2, oddly_breakable_by_hand=1}, + sound = "shooter_shotgun", + particle = "smoke_puff.png", + }) + itemstack:add_wear(1311) -- 50 Rounds + return itemstack + end, +}) + minetest.register_craft({ output = "shooter:pistol", recipe = { @@ -45,3 +64,12 @@ minetest.register_craft({ }, }) +minetest.register_craft({ + output = "shooter:shotgun", + recipe = { + {"default:steel_ingot", "", ""}, + {"", "default:steel_ingot", ""}, + {"", "default:mese_crystal", "default:bronze_ingot"}, + }, +}) + diff --git a/shooter.conf.example b/shooter.conf.example new file mode 100644 index 0000000..bc44e8c --- /dev/null +++ b/shooter.conf.example @@ -0,0 +1,13 @@ +-- Simple Shooter config example + +-- Global Constants (defaults) + +-- Particle texture used when target it hit +SHOOTER_EXPLOSION_TEXTURE = "shooter_hit.png" + +-- Allow entities in multiplayer mode +SHOOTER_ALLOW_ENTITIES = false + +-- Maximum object range, applies only if entities are allowed +SHOOTER_OBJECT_RANGE = 50 + diff --git a/shooter.lua b/shooter.lua index 7d4218c..332d425 100644 --- a/shooter.lua +++ b/shooter.lua @@ -1,34 +1,145 @@ shooter = {} -local function in_range(r, x) - local chance = r.a * (x * x) + r.b * x + r.c - if math.random(100) < chance then +SHOOTER_EXPLOSION_TEXTURE = "shooter_hit.png" +SHOOTER_ALLOW_ENTITIES = true +SHOOTER_OBJECT_RANGE = 50 + +local modpath = minetest.get_modpath(minetest.get_current_modname()) +local input = io.open(modpath.."/shooter.conf", "r") +if input then + dofile(modpath.."/shooter.conf") + input:close() + input = nil +end +if minetest.is_singleplayer() == true then + SHOOTER_ALLOW_ENTITIES = true +end +local timer = 0 +local shots = {} + +local function spawn_particles(p, v, d, texture) + local pos = vector.add(p, vector.multiply(v, {x=d, y=d, z=d})) + pos.y = pos.y + 0.75 + local spread = {x=0.1, y=0.1, z=0.1} + minetest.add_particlespawner(15, 0.3, + vector.subtract(pos, spread), vector.add(pos, spread), + {x=-1, y=1, z=-1}, {x=1, y=2, z=1}, + {x=-2, y=-2, z=-2}, {x=2, y=-2, z=2}, + 0.1, 0.75, 1, 2, false, texture + ) +end + +local function is_valid_object(object) + if object:is_player() == true then return true end + if SHOOTER_ALLOW_ENTITIES == true then + local luaentity = object:get_luaentity() + if luaentity then + if minetest.registered_entities[luaentity.name] then + return true + end + end + end return false end -function shooter:fire_weapon(user, def) +function shooter:fire_weapon(user, pointed_thing, def) + local name = user:get_player_name() + if shots[name] then + if timer < shots[name] then + return + end + end + shots[name] = timer + def.tool_caps.full_punch_interval minetest.sound_play(def.sound, {object=user}) - local target = {player=nil, distance=50} + local v1 = user:get_look_dir() local p1 = user:getpos() - for _,player in ipairs(minetest.get_connected_players()) do - local p2 = player:getpos() - if p1 and p2 then - local x = vector.distance(p1, p2) - p2.y = p2.y - 0.75 - if x > 0 and x < target.distance and x < 50 then - if in_range(def.range_func, x) == true then - local v1 = user:get_look_dir() + 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" then + local pos = minetest.get_pointed_thing_position(pointed_thing, false) + local node = minetest.get_node(pos) + if not node then + return + end + local item = minetest.registered_items[node.name] + if not item.groups then + return + end + for k, v in pairs(def.groups) do + local level = item.groups[k] or 0 + if level >= v then + minetest.remove_node(pos) + local sounds = item.sounds + if sounds then + local soundspec = sounds.dug + if soundspec then + soundspec.pos = pos + minetest.sound_play(soundspec.name, soundspec) + end + end + local tiles = item.tiles + if tiles then + if tiles[1] then + spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z}, + v1, vector.distance(p1, pos), tiles[1]) + end + end + break + end + end + return + elseif pointed_thing.type == "object" then + local object = pointed_thing.ref + if is_valid_object(object) == true then + object:punch(user, nil, def.tool_caps, v1) + local p2 = object:getpos() + spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z}, v1, + vector.distance(p1, p2), SHOOTER_EXPLOSION_TEXTURE) + return + end + end + if def.range > 100 then + def.range = 100 + end + local target = {object=nil, distance=100} + local objects = {} + if SHOOTER_ALLOW_ENTITIES == true then + local range = def.range + if range > SHOOTER_OBJECT_RANGE then + range = SHOOTER_OBJECT_RANGE + end + local r = math.ceil(range * 0.5) + local p = vector.add(p1, vector.multiply(v1, {x=r, y=r, z=r})) + objects = minetest.get_objects_inside_radius(p, r) + else + objects = minetest.get_connected_players() + end + for _,object in ipairs(objects) do + if is_valid_object(object) == true then + local p2 = object:getpos() + if p1 and p2 then + local x = vector.distance(p1, p2) + p2.y = p2.y - 0.75 + if x > 0 and x < target.distance and x < def.range then + local yx = 0 + if x > 50 then + yx = 0.001 * (10 - x * 0.1) + else + yx = 0.000015 * (x * x) - 0.0015 * x + 0.042 + end + local yy = yx * 3 local v2 = vector.normalize(vector.direction(p1, p2)) local vd = vector.subtract(v1, v2) - local yx = 0.00002 * (x * x) - 0.002 * x + 0.05 - local yy = yx * 3 if math.abs(vd.x) < yx and - math.abs(vd.z) < yx and - math.abs(vd.y) < yy then + math.abs(vd.z) < yx and + math.abs(vd.y) < yy then target = { - player = player, + object = object, distance = x, direction = v1, pos1 = {x=p1.x, z=p1.z, y=p1.y+1}, @@ -39,9 +150,11 @@ function shooter:fire_weapon(user, def) end end end - if target.player then + if target.object then if minetest.line_of_sight(target.pos1, target.pos2, 1) then - target.player:punch(user, nil, def.tool_caps, target.direction) + target.object:punch(user, nil, def.tool_caps, target.direction) + spawn_particles(target.pos1, target.direction, + target.distance, SHOOTER_EXPLOSION_TEXTURE) end end end @@ -50,3 +163,7 @@ minetest.register_on_joinplayer(function(player) player:hud_set_flags({crosshair = true}) end) +minetest.register_globalstep(function(dtime) + timer = timer + dtime +end) + diff --git a/sounds/shooter_pistol.ogg b/sounds/shooter_pistol.ogg index 14fc786..0d3f464 100644 Binary files a/sounds/shooter_pistol.ogg and b/sounds/shooter_pistol.ogg differ diff --git a/sounds/shooter_shotgun.ogg b/sounds/shooter_shotgun.ogg new file mode 100644 index 0000000..22aa8d4 Binary files /dev/null and b/sounds/shooter_shotgun.ogg differ diff --git a/textures/shooter_hit.png b/textures/shooter_hit.png new file mode 100644 index 0000000..46d95b2 Binary files /dev/null and b/textures/shooter_hit.png differ diff --git a/textures/shooter_shotgun.png b/textures/shooter_shotgun.png new file mode 100644 index 0000000..903223b Binary files /dev/null and b/textures/shooter_shotgun.png differ