Version 0.4.0 (rework)

This commit is contained in:
stujones11 2014-06-02 19:19:22 +01:00
parent 40e7645a43
commit a19923e6aa
8 changed files with 325 additions and 141 deletions

View file

@ -1,28 +1,21 @@
Minetest Mod - Simple Shooter [shooter] Minetest Mod - Simple Shooter [shooter]
======================================= =======================================
Mod Version: 0.3.0 Mod Version: 0.4.0
Minetest Version: 0.4.8-dev d9ef072305 Minetest Version: 0.4.9
Depends: default An experimental first person shooter mod that uses simple vector mathematics
to produce an accurate and server-firendly method of hit detection.
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.
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)
By default this mod is configured to work only against other players in By default this mod is configured to work only against other players in
multiplayer (server) mode. This is overridden in singleplayer mode to work multiplayer mode and against Simple Mobs [mobs] in singleplayer mode.
against all registered entities instead.
Default configuration can be customised by adding a shooter.conf file to the Default configuration can be customised by adding a shooter.conf file to
mod's main directory, see shooter.conf.example for more details. the mod's main directory, see shooter.conf.example for more details.
This is still very much a work in progress which I plan to eventually use as This is still very much a work in progress which I eventually plan to use
the base for a 'Spades' style FPS game using the minetest engine. as the base for a 'Spades' style FPS game using the minetest engine.
Crafting Crafting
======== ========
@ -34,7 +27,7 @@ M = Mese Crystal [default:mese_crysytal]
Pistol: [shooter:pistol] Pistol: [shooter:pistol]
+---+---+ +---+---+
| S | B | | S | S |
+---+---+ +---+---+
| | M | | | M |
+---+---+ +---+---+
@ -59,3 +52,13 @@ Shotgun: [shooter:shotgun]
| | M | B | | | M | B |
+---+---+---+ +---+---+---+
Sub Machine Gun: [shooter:machine_gun]
+---+---+---+
| S | S | S |
+---+---+---+
| | B | M |
+---+---+---+
| | B | |
+---+---+---+

View file

@ -1,2 +0,0 @@
default

View file

@ -4,12 +4,15 @@ minetest.register_tool("shooter:pistol", {
description = "Pistol", description = "Pistol",
inventory_image = "shooter_pistol.png", inventory_image = "shooter_pistol.png",
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local name = user:get_player_name()
shooter:fire_weapon(user, pointed_thing, { shooter:fire_weapon(user, pointed_thing, {
range = 30, name = name,
tool_caps = {full_punch_interval=0.5, damage_groups={fleshy=1}}, range = 100,
step = 20,
tool_caps = {full_punch_interval=0.5, damage_groups={fleshy=2}},
groups = {snappy=3, fleshy=3, oddly_breakable_by_hand=3}, groups = {snappy=3, fleshy=3, oddly_breakable_by_hand=3},
sound = "shooter_pistol", sound = "shooter_pistol",
particle = "default_obsidian.png", particle = "shooter_cap.png",
}) })
itemstack:add_wear(328) -- 200 Rounds itemstack:add_wear(328) -- 200 Rounds
return itemstack return itemstack
@ -20,12 +23,15 @@ minetest.register_tool("shooter:riffle", {
description = "Riffle", description = "Riffle",
inventory_image = "shooter_riffle.png", inventory_image = "shooter_riffle.png",
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local name = user:get_player_name()
shooter:fire_weapon(user, pointed_thing, { shooter:fire_weapon(user, pointed_thing, {
range = 80, name = name,
tool_caps = {full_punch_interval=1.0, damage_groups={fleshy=2}}, range = 200,
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}, groups = {snappy=3, crumbly=3, choppy=3, fleshy=2, oddly_breakable_by_hand=2},
sound = "shooter_riffle", sound = "shooter_riffle",
particle = "default_gold_block.png", particle = "shooter_bullet.png",
}) })
itemstack:add_wear(656) -- 100 Rounds itemstack:add_wear(656) -- 100 Rounds
return itemstack return itemstack
@ -36,8 +42,11 @@ minetest.register_tool("shooter:shotgun", {
description = "Shotgun", description = "Shotgun",
inventory_image = "shooter_shotgun.png", inventory_image = "shooter_shotgun.png",
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local name = user:get_player_name()
shooter:fire_weapon(user, pointed_thing, { shooter:fire_weapon(user, pointed_thing, {
range = 15, name = name,
range = 50,
step = 15,
tool_caps = {full_punch_interval=1.5, damage_groups={fleshy=4}}, 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}, groups = {cracky=3, snappy=2, crumbly=2, choppy=2, fleshy=1, oddly_breakable_by_hand=1},
sound = "shooter_shotgun", sound = "shooter_shotgun",
@ -48,10 +57,33 @@ minetest.register_tool("shooter:shotgun", {
end, end,
}) })
minetest.register_tool("shooter:machine_gun", {
description = "Sub Machine Gun",
inventory_image = "shooter_smgun.png",
on_use = function(itemstack, user, pointed_thing)
local name = user:get_player_name()
for i = 0, 0.45, 0.15 do
minetest.after(i, function()
shooter:fire_weapon(user, pointed_thing, {
name = name,
range = 100,
step = 20,
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",
})
end)
end
itemstack:add_wear(328) -- 4 x 200 Rounds
return itemstack
end,
})
minetest.register_craft({ minetest.register_craft({
output = "shooter:pistol", output = "shooter:pistol",
recipe = { recipe = {
{"default:steel_ingot", "default:bronze_ingot"}, {"default:steel_ingot", "default:steel_ingot"},
{"", "default:mese_crystal"}, {"", "default:mese_crystal"},
}, },
}) })
@ -74,3 +106,12 @@ minetest.register_craft({
}, },
}) })
minetest.register_craft({
output = "shooter:machine_gun",
recipe = {
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
{"", "default:bronze_ingot", "default:mese_crystal"},
{"", "default:bronze_ingot", ""},
},
})

View file

@ -2,18 +2,42 @@
-- Global Constants (defaults) -- Global Constants (defaults)
-- Particle texture used when target it hit -- Particle texture used when a player or entity it hit
SHOOTER_EXPLOSION_TEXTURE = "shooter_hit.png" SHOOTER_EXPLOSION_TEXTURE = "shooter_hit.png"
-- Allow node destruction -- Allow node destruction
SHOOTER_ALLOW_NODES = true SHOOTER_ALLOW_NODES = true
-- Maximum node rage, applies only if nodes are allowed
SHOOTER_NODE_RANGE = 50
-- Allow entities in multiplayer mode -- Allow entities in multiplayer mode
SHOOTER_ALLOW_ENTITIES = false SHOOTER_ALLOW_ENTITIES = false
-- Maximum object range, applies only if entities are allowed -- Allow players in multiplayer mode
SHOOTER_OBJECT_RANGE = 50 SHOOTER_ALLOW_PLAYERS = true
-- How often objects are fully reloaded
SHOOTER_OBJECT_RELOAD_TIME = 1
-- How often object positions are updated
SHOOTER_OBJECT_UPDATE_TIME = 0.25
-- How often rounds are processed
SHOOTER_ROUNDS_UPDATE_TIME = 0.4
-- Player collision box offset (may require adjustment for some games)
SHOOTER_PLAYER_OFFSET = {x=0, y=1, z=0}
-- Entity collision box offset (may require adjustment for other mobs)
SHOOTER_ENTITY_OFFSET = {x=0, y=0, z=0}
-- Shootable entities (default support for Simple Mobs)
SHOOTER_ENTITIES = {
"mobs:dirt_monster",
"mobs:stone_monster",
"mobs:sand_monster",
"mobs:tree_monster",
"mobs:sheep",
"mobs:rat",
"mobs:oerkki",
"mobs:dungeon_master",
}

View file

@ -1,10 +1,39 @@
shooter = {} shooter = {
time = 0,
objects = {},
rounds = {},
shots = {},
}
SHOOTER_EXPLOSION_TEXTURE = "shooter_hit.png" SHOOTER_EXPLOSION_TEXTURE = "shooter_hit.png"
SHOOTER_ALLOW_NODES = true SHOOTER_ALLOW_NODES = true
SHOOTER_ALLOW_ENTITIES = false SHOOTER_ALLOW_ENTITIES = false
SHOOTER_NODE_RANGE = 50 SHOOTER_ALLOW_PLAYERS = true
SHOOTER_OBJECT_RANGE = 50 SHOOTER_OBJECT_RELOAD_TIME = 1
SHOOTER_OBJECT_UPDATE_TIME = 0.25
SHOOTER_ROUNDS_UPDATE_TIME = 0.4
SHOOTER_PLAYER_OFFSET = {x=0, y=1, z=0}
SHOOTER_ENTITY_OFFSET = {x=0, y=0, z=0}
SHOOTER_ENTITIES = {
"mobs:dirt_monster",
"mobs:stone_monster",
"mobs:sand_monster",
"mobs:tree_monster",
"mobs:sheep",
"mobs:rat",
"mobs:oerkki",
"mobs:dungeon_master",
}
if minetest.is_singleplayer() == true then
SHOOTER_ALLOW_ENTITIES = true
SHOOTER_ALLOW_PLAYERS = false
end
local allowed_entities = {}
for _,v in ipairs(SHOOTER_ENTITIES) do
allowed_entities[v] = 1
end
local modpath = minetest.get_modpath(minetest.get_current_modname()) local modpath = minetest.get_modpath(minetest.get_current_modname())
local input = io.open(modpath.."/shooter.conf", "r") local input = io.open(modpath.."/shooter.conf", "r")
@ -13,13 +42,17 @@ if input then
input:close() input:close()
input = nil input = nil
end end
if minetest.is_singleplayer() == true then
SHOOTER_ALLOW_ENTITIES = true local rounds_update_time = 0
local object_update_time = 0
local object_reload_time = 0
local function get_dot_product(v1, v2)
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
end end
local timer = 0
local shots = {}
local function spawn_particles(p, v, d, texture) local function spawn_particles(p, v, d, texture)
if p and v and d then
if type(texture) ~= "string" then if type(texture) ~= "string" then
texture = SHOOTER_EXPLOSION_TEXTURE texture = SHOOTER_EXPLOSION_TEXTURE
end end
@ -32,20 +65,25 @@ local function spawn_particles(p, v, d, texture)
{x=-2, y=-2, z=-2}, {x=2, y=-2, z=2}, {x=-2, y=-2, z=-2}, {x=2, y=-2, z=2},
0.1, 0.75, 1, 2, false, texture 0.1, 0.75, 1, 2, false, texture
) )
end
end end
local function is_valid_object(object) local function is_valid_object(object)
if object then
if object:is_player() == true then if object:is_player() == true then
return true return true
end end
if SHOOTER_ALLOW_ENTITIES == true then if SHOOTER_ALLOW_ENTITIES == true then
local luaentity = object:get_luaentity() local luaentity = object:get_luaentity()
if luaentity then if luaentity then
if minetest.registered_entities[luaentity.name] then if luaentity.name then
if allowed_entities[luaentity.name] then
return true return true
end end
end end
end end
end
end
return false return false
end end
@ -64,16 +102,15 @@ local function punch_node(pos, def)
if level >= v then if level >= v then
minetest.remove_node(pos) minetest.remove_node(pos)
local sounds = item.sounds local sounds = item.sounds
if sounds then if item.sounds then
local soundspec = sounds.dug local spec = item.sounds.dug
if soundspec then if spec then
soundspec.pos = pos spec.pos = pos
minetest.sound_play(soundspec.name, soundspec) minetest.sound_play(spec.name, spec)
end end
end end
local tiles = item.tiles if item.tiles then
if tiles then return item.tiles[1]
return tiles[1]
end end
break break
end end
@ -81,17 +118,81 @@ local function punch_node(pos, def)
end end
end end
local function 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 x = vector.distance(p1, p2)
if x < round.def.step then
local n = vector.multiply(v1, {x=-1, y=0, z=-1})
local v2 = vector.subtract(p1, p2)
local r1 = get_dot_product(n, v2)
local r2 = get_dot_product(n, v1)
if r2 ~= 0 then
local t = -(r1 / r2)
local td = vector.multiply(v1, {x=t, y=t, z=t})
local pt = vector.add(p1, td)
local pd = vector.subtract(pt, p2)
if math.abs(pd.x) < ref.collisionbox[4] and
math.abs(pd.y) < ref.collisionbox[5] and
math.abs(pd.z) < ref.collisionbox[6] then
target.object = ref.object
target.pos = pt
target.distance = x
end
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)
spawn_particles({x=p1.x, y=p1.y - 1, z=p1.z}, v1,
target.distance, SHOOTER_EXPLOSION_TEXTURE)
end
return 1
elseif pos and SHOOTER_ALLOW_NODES == true then
local texture = punch_node(pos, round.def)
if texture then
spawn_particles({x=p1.x, y=p1.y - 1, z=p1.z},
v1, vector.distance(p1, pos), texture)
end
return 1
end
elseif SHOOTER_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
local texture = punch_node(pos, round.def)
if texture then
spawn_particles({x=p1.x, y=p1.y - 1, z=p1.z},
v1, vector.distance(p1, pos), texture)
end
return 1
end
end
end
function shooter:fire_weapon(user, pointed_thing, def) function shooter:fire_weapon(user, pointed_thing, def)
local name = user:get_player_name() if shooter.shots[def.name] then
if shots[name] then if shooter.time < shooter.shots[def.name] then
if timer < shots[name] then
return return
end end
end end
shots[name] = timer + def.tool_caps.full_punch_interval shooter.shots[def.name] = shooter.time + def.tool_caps.full_punch_interval
minetest.sound_play(def.sound, {object=user}) minetest.sound_play(def.sound, {object=user})
local v1 = user:get_look_dir() local v1 = user:get_look_dir()
local p1 = user:getpos() local p1 = user:getpos()
if not v1 or not p1 then
return
end
minetest.add_particle({x=p1.x, y=p1.y + 1.6, z=p1.z}, minetest.add_particle({x=p1.x, y=p1.y + 1.6, z=p1.z},
vector.multiply(v1, {x=30, y=30, z=30}), vector.multiply(v1, {x=30, y=30, z=30}),
{x=0, y=0, z=0}, 0.5, 0.25, {x=0, y=0, z=0}, 0.5, 0.25,
@ -104,7 +205,6 @@ function shooter:fire_weapon(user, pointed_thing, def)
spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z}, spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z},
v1, vector.distance(p1, pos), texture) v1, vector.distance(p1, pos), texture)
end end
return
elseif pointed_thing.type == "object" then elseif pointed_thing.type == "object" then
local object = pointed_thing.ref local object = pointed_thing.ref
if is_valid_object(object) == true then if is_valid_object(object) == true then
@ -112,82 +212,81 @@ function shooter:fire_weapon(user, pointed_thing, def)
local p2 = object:getpos() local p2 = object:getpos()
spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z}, v1, spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z}, v1,
vector.distance(p1, p2), SHOOTER_EXPLOSION_TEXTURE) vector.distance(p1, p2), SHOOTER_EXPLOSION_TEXTURE)
return
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,
})
end end
if def.range > 100 then end
def.range = 100
end function shooter:load_objects()
local target = {object=nil, distance=100}
local objects = {} local objects = {}
if SHOOTER_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
end
end
if SHOOTER_ALLOW_ENTITIES == true then if SHOOTER_ALLOW_ENTITIES == true then
local range = def.range for _,ref in pairs(minetest.luaentities) do
if range > SHOOTER_OBJECT_RANGE then if ref.object and ref.name then
range = SHOOTER_OBJECT_RANGE if allowed_entities[ref.name] then
end local pos = ref.object:getpos()
local r = math.ceil(range * 0.5) local hp = ref.object:get_hp() or 0
local p = vector.add(p1, vector.multiply(v1, {x=r, y=r, z=r})) if pos and hp > 0 then
objects = minetest.get_objects_inside_radius(p, r) local def = minetest.registered_entities[ref.name]
else table.insert(objects, {
objects = minetest.get_connected_players() name = ref.name,
end object = ref.object,
for _,object in ipairs(objects) do pos = pos,
if is_valid_object(object) == true then collisionbox = def.collisionbox,
local p2 = object:getpos() offset = SHOOTER_ENTITY_OFFSET,
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 > 30 then
yx = 0.001 * (10 - x * 0.1)
else
yx = 0.00002 * (x * x) - 0.002 * x + 0.05
end
local yy = yx * 3
local v2 = vector.normalize(vector.direction(p1, p2))
local vd = vector.subtract(v1, v2)
if math.abs(vd.x) < yx and
math.abs(vd.z) < yx and
math.abs(vd.y) < yy then
target = {
object = object,
distance = x,
pos = {x=p2.x, z=p2.z, y=p2.y+1.75},
}
end end
end end
end end
end end
end end
local view_pos = {x=p1.x, y=p1.y + 1.75, z=p1.z} object_reload_time = shooter.time
if target.object then object_update_time = shooter.time
local success, pos = minetest.line_of_sight(view_pos, target.pos, 1) shooter.objects = {}
if success then for _,v in ipairs(objects) do
target.object:punch(user, nil, def.tool_caps, v1) table.insert(shooter.objects, v)
spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z}, v1,
target.distance, SHOOTER_EXPLOSION_TEXTURE)
elseif pos and SHOOTER_ALLOW_NODES == true then
local texture = punch_node(pos, def)
if texture then
spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z},
v1, vector.distance(p1, pos), texture)
end end
end end
elseif SHOOTER_ALLOW_NODES == true then
local d = def.range function shooter:update_objects()
if d > SHOOTER_NODE_RANGE then if shooter.time - object_reload_time > SHOOTER_OBJECT_RELOAD_TIME then
d = SHOOTER_NODE_RANGE shooter:load_objects()
end elseif shooter.time - object_update_time > SHOOTER_OBJECT_UPDATE_TIME then
local p2 = vector.add(view_pos, vector.multiply(v1, {x=d, y=d, z=d})) for i, ref in ipairs(shooter.objects) do
local success, pos = minetest.line_of_sight(view_pos, p2, 1) if ref.object then
local pos = ref.object:getpos()
if pos then if pos then
local texture = punch_node(pos, def) shooter.objects[i].pos = pos
if texture then end
spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z}, else
v1, vector.distance(p1, pos), texture) table.remove(shooter.objects, i)
end end
end end
object_update_time = shooter.time
end end
end end
@ -196,6 +295,25 @@ minetest.register_on_joinplayer(function(player)
end) end)
minetest.register_globalstep(function(dtime) minetest.register_globalstep(function(dtime)
timer = timer + dtime shooter.time = shooter.time + dtime
if shooter.time - rounds_update_time > SHOOTER_ROUNDS_UPDATE_TIME then
for i, round in ipairs(shooter.rounds) do
if 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
object_reload_time = 0
object_update_time = 0
shooter.time = 0
end
end) end)

BIN
textures/shooter_bullet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

BIN
textures/shooter_cap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

BIN
textures/shooter_smgun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B