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,36 +42,45 @@ 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 type(texture) ~= "string" then if p and v and d then
texture = SHOOTER_EXPLOSION_TEXTURE if type(texture) ~= "string" then
texture = SHOOTER_EXPLOSION_TEXTURE
end
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 end
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 end
local function is_valid_object(object) local function is_valid_object(object)
if object:is_player() == true then if object then
return true if object:is_player() == true then
end return true
if SHOOTER_ALLOW_ENTITIES == true then end
local luaentity = object:get_luaentity() if SHOOTER_ALLOW_ENTITIES == true then
if luaentity then local luaentity = object:get_luaentity()
if minetest.registered_entities[luaentity.name] then if luaentity then
return true if luaentity.name then
if allowed_entities[luaentity.name] then
return true
end
end
end end
end end
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,
@ -102,92 +203,90 @@ function shooter:fire_weapon(user, pointed_thing, def)
local texture = punch_node(pos, def) local texture = punch_node(pos, def)
if texture then if texture then
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
object:punch(user, nil, def.tool_caps, v1) object:punch(user, nil, def.tool_caps, v1)
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
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 else
objects = minetest.get_connected_players() 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
for _,object in ipairs(objects) do end
if is_valid_object(object) == true then
local p2 = object:getpos() function shooter:load_objects()
if p1 and p2 then local objects = {}
local x = vector.distance(p1, p2) if SHOOTER_ALLOW_PLAYERS == true then
p2.y = p2.y - 0.75 local players = minetest.get_connected_players()
if x > 0 and x < target.distance and x < def.range then for _,player in ipairs(players) do
local yx = 0 local pos = player:getpos()
if x > 30 then local name = player:get_player_name()
yx = 0.001 * (10 - x * 0.1) local hp = player:get_hp() or 0
else if pos and name and hp > 0 then
yx = 0.00002 * (x * x) - 0.002 * x + 0.05 table.insert(objects, {
end name = name,
local yy = yx * 3 object = player,
local v2 = vector.normalize(vector.direction(p1, p2)) pos = pos,
local vd = vector.subtract(v1, v2) collisionbox = {-0.25,-1.0,-0.25, 0.25, 0.8, 0.25},
if math.abs(vd.x) < yx and offset = SHOOTER_PLAYER_OFFSET,
math.abs(vd.z) < yx and })
math.abs(vd.y) < yy then end
target = { end
object = object, end
distance = x, if SHOOTER_ALLOW_ENTITIES == true then
pos = {x=p2.x, z=p2.z, y=p2.y+1.75}, 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,
offset = SHOOTER_ENTITY_OFFSET,
})
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, end
target.distance, SHOOTER_EXPLOSION_TEXTURE) end
elseif pos and SHOOTER_ALLOW_NODES == true then
local texture = punch_node(pos, def) function shooter:update_objects()
if texture then if shooter.time - object_reload_time > SHOOTER_OBJECT_RELOAD_TIME then
spawn_particles({x=p1.x, y=p1.y + 0.75, z=p1.z}, shooter:load_objects()
v1, vector.distance(p1, pos), texture) elseif shooter.time - object_update_time > SHOOTER_OBJECT_UPDATE_TIME then
end for i, ref in ipairs(shooter.objects) do
end if ref.object then
elseif SHOOTER_ALLOW_NODES == true then local pos = ref.object:getpos()
local d = def.range if pos then
if d > SHOOTER_NODE_RANGE then shooter.objects[i].pos = pos
d = SHOOTER_NODE_RANGE end
end else
local p2 = vector.add(view_pos, vector.multiply(v1, {x=d, y=d, z=d})) table.remove(shooter.objects, i)
local success, pos = minetest.line_of_sight(view_pos, p2, 1)
if pos 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
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