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]
=======================================
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 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)
An experimental first person shooter mod that uses simple vector mathematics
to produce an accurate and server-firendly method of hit detection.
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.
multiplayer mode and against Simple Mobs [mobs] in singleplayer mode.
Default configuration can be customised by adding a shooter.conf file to the
mod's main directory, see shooter.conf.example for more details.
Default configuration can be customised by adding a shooter.conf file to
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
the base for a 'Spades' style FPS game using the minetest engine.
This is still very much a work in progress which I eventually plan to use
as the base for a 'Spades' style FPS game using the minetest engine.
Crafting
========
@ -34,7 +27,7 @@ M = Mese Crystal [default:mese_crysytal]
Pistol: [shooter:pistol]
+---+---+
| S | B |
| S | S |
+---+---+
| | M |
+---+---+
@ -59,3 +52,13 @@ Shotgun: [shooter:shotgun]
| | 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",
inventory_image = "shooter_pistol.png",
on_use = function(itemstack, user, pointed_thing)
local name = user:get_player_name()
shooter:fire_weapon(user, pointed_thing, {
range = 30,
tool_caps = {full_punch_interval=0.5, damage_groups={fleshy=1}},
name = name,
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},
sound = "shooter_pistol",
particle = "default_obsidian.png",
particle = "shooter_cap.png",
})
itemstack:add_wear(328) -- 200 Rounds
return itemstack
@ -20,12 +23,15 @@ minetest.register_tool("shooter:riffle", {
description = "Riffle",
inventory_image = "shooter_riffle.png",
on_use = function(itemstack, user, pointed_thing)
local name = user:get_player_name()
shooter:fire_weapon(user, pointed_thing, {
range = 80,
tool_caps = {full_punch_interval=1.0, damage_groups={fleshy=2}},
name = name,
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},
sound = "shooter_riffle",
particle = "default_gold_block.png",
particle = "shooter_bullet.png",
})
itemstack:add_wear(656) -- 100 Rounds
return itemstack
@ -36,8 +42,11 @@ minetest.register_tool("shooter:shotgun", {
description = "Shotgun",
inventory_image = "shooter_shotgun.png",
on_use = function(itemstack, user, pointed_thing)
local name = user:get_player_name()
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}},
groups = {cracky=3, snappy=2, crumbly=2, choppy=2, fleshy=1, oddly_breakable_by_hand=1},
sound = "shooter_shotgun",
@ -48,10 +57,33 @@ minetest.register_tool("shooter:shotgun", {
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({
output = "shooter:pistol",
recipe = {
{"default:steel_ingot", "default:bronze_ingot"},
{"default:steel_ingot", "default:steel_ingot"},
{"", "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)
-- Particle texture used when target it hit
-- Particle texture used when a player or entity it hit
SHOOTER_EXPLOSION_TEXTURE = "shooter_hit.png"
-- Allow node destruction
SHOOTER_ALLOW_NODES = true
-- Maximum node rage, applies only if nodes are allowed
SHOOTER_NODE_RANGE = 50
-- Allow entities in multiplayer mode
SHOOTER_ALLOW_ENTITIES = false
-- Maximum object range, applies only if entities are allowed
SHOOTER_OBJECT_RANGE = 50
-- Allow players in multiplayer mode
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_ALLOW_NODES = true
SHOOTER_ALLOW_ENTITIES = false
SHOOTER_NODE_RANGE = 50
SHOOTER_OBJECT_RANGE = 50
SHOOTER_ALLOW_PLAYERS = true
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 input = io.open(modpath.."/shooter.conf", "r")
@ -13,36 +42,45 @@ if input then
input:close()
input = nil
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
local timer = 0
local shots = {}
local function spawn_particles(p, v, d, texture)
if type(texture) ~= "string" then
texture = SHOOTER_EXPLOSION_TEXTURE
if p and v and d then
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
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
if object then
if object:is_player() == true then
return true
end
if SHOOTER_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
end
end
end
@ -64,16 +102,15 @@ local function punch_node(pos, def)
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)
if item.sounds then
local spec = item.sounds.dug
if spec then
spec.pos = pos
minetest.sound_play(spec.name, spec)
end
end
local tiles = item.tiles
if tiles then
return tiles[1]
if item.tiles then
return item.tiles[1]
end
break
end
@ -81,17 +118,81 @@ local function punch_node(pos, def)
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)
local name = user:get_player_name()
if shots[name] then
if timer < shots[name] then
if shooter.shots[def.name] then
if shooter.time < shooter.shots[def.name] then
return
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})
local v1 = user:get_look_dir()
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},
vector.multiply(v1, {x=30, y=30, z=30}),
{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)
if texture then
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
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
vector.distance(p1, p2), SHOOTER_EXPLOSION_TEXTURE)
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()
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
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 > 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
function shooter:load_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
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
local view_pos = {x=p1.x, y=p1.y + 1.75, z=p1.z}
if target.object then
local success, pos = minetest.line_of_sight(view_pos, target.pos, 1)
if success then
target.object:punch(user, nil, def.tool_caps, v1)
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
elseif SHOOTER_ALLOW_NODES == true then
local d = def.range
if d > SHOOTER_NODE_RANGE then
d = SHOOTER_NODE_RANGE
end
local p2 = vector.add(view_pos, vector.multiply(v1, {x=d, y=d, z=d}))
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)
object_reload_time = shooter.time
object_update_time = shooter.time
shooter.objects = {}
for _,v in ipairs(objects) do
table.insert(shooter.objects, v)
end
end
function shooter:update_objects()
if shooter.time - object_reload_time > SHOOTER_OBJECT_RELOAD_TIME then
shooter:load_objects()
elseif shooter.time - object_update_time > SHOOTER_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
object_update_time = shooter.time
end
end
@ -196,6 +295,25 @@ minetest.register_on_joinplayer(function(player)
end)
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)

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