Upload files to 'mods/gunkit'

This commit is contained in:
thunderdog1138 2020-11-17 21:19:06 +00:00
parent bd0dab3eb5
commit a1cf7e5646
5 changed files with 667 additions and 0 deletions

129
mods/gunkit/README.md Normal file
View File

@ -0,0 +1,129 @@
# gunkit
Adds an api to easily register advanced firearms within Minetest.
## Features
* Full auto guns
* Mags
* Mag Types
* Alternate fire mode
* ADS (Aim Down Sights)
## Usage (mods):
Registering a firearm:
`gunkit.register_firearm(name, firearm definition)`
Registering a mag:
`gunkit.register_mag(name, mag definition)`
Firearm Definition
------------------
Used by `gunkit.register_firearm`
```
{
description = "Super cool Firearm",
wield_scale = {x = 1, y = 1, z = 1},
-- Firearm wield scale (see Minetest lua_api.txt).
inventory_image = "your_firearm.png",
mag_type = "smg",
-- Type of mag the gun will load.
sounds = {
mag_load = "mymod:mag_load",
-- Sound to be played when loading a mag.
mag_drop = "mymod:mag_drop",
-- Sound to be played when dropping a mag.
fire_empty = "mymod:fire_empty",
-- Sound to be played when firing a gun with an empty mag.
},
-- Table fields used for both fire and alt_fire.
{
bullet_texture = "my_bullet.png",
-- Texture to be used for bullet projectiles.
sounds = {
fire = "mymod:fire",
-- Sound to be played when firing the gun.
shell_drop = "mymod:shell_drop",
-- Sound to be played when firing the gun.
fire_toggle = "mymod:fire_toggle",
-- Sound to be played when swapping from this fire mode.
},
range = 60,
-- Firearms bullet range.
speed = 300,
-- Speed of bullet projectiles, time to projectile hit is always range / speed.
spread = 1,
-- Max bullet spread (in degrees).
dmg = 3,
-- Bullet damage.
shots = 1,
-- Amount of bullets fired each shot.
interval = 0.1,
-- Time between shots (in seconds).
zoom = 2,
-- Level of zoom when using ADS (if applicable).
zoom_time = 1,
-- Time in seconds till fov hits fov*zoom
},
callbacks = {
-- Table fields used for both fire and alt_fire
fire = {
on_fire = function({itemstack, user}),
-- Function to be called when firearm is used.
-- Return false to prevent default behavior.
on_hit = function({itemstack, hitter, object}),
-- Function to be called when an object is hit by a bullet.
-- Return false to prevent default behavior.
},
on_drop_mag = function({itemstack, user}),
-- Function to be called when a player attempts to drop a guns mag.
-- Return false to prevent default behavior.
on_load_mag = function({itemstack, user}),
-- Function to be called when a player attempts to load a gun.
-- Return false to prevent default behavior.
},
}
```
Magazine Definition
-------------------
Used by `gunkit.register_mag`
```
{
description = "Super cool gun magazine",
inventory_image = "your_firearm_magazine.png",
mag_type = "smg",
-- Set this magazine as loadable by all guns with corresponding mag type.
ammo = "mymod:bullet",
-- Ammo used by this magazine (Itemstring).
max_ammo = 120,
-- Amount of ammo this magazine can hold.
}
```

518
mods/gunkit/api.lua Normal file
View File

@ -0,0 +1,518 @@
gunkit = {}
gunkit.firing = {}
gunkit.timer = {}
--Interval for gun-cooldown checks
gunkit.count_interval = 0.1
--
-- Initate some helper functions
--
function get_random_float(lower, upper)
return lower + math.random() * (upper - lower)
end
function get_table_size(tbl)
local count = 0
for k, v in pairs(tbl) do
count = count + 1
end
return count
end
--
-- Lets define some functions to register guns/mags
--
function gunkit.register_firearm(name, def)
--Check if mag_type and bullet textures are specified
if not def.mag_type or not def.fire.bullet_texture or def.alt_fire
and not def.alt_fire.bullet_texture then return end
--Define some default values
local fire = {
range = 60,
speed = 300,
spread = 1,
dmg = 7,
shots = 1,
interval = 0.15,
zoom = 2,
zoom_time = 2,
}
--Use defaults for any value not set if def.fire is present
if def.fire then
for k, value in pairs(def.fire) do
fire[k] = value
end
end
--Get callbacks table
local calls = def.callbacks
--Fancy item descrips
local descrip = {
def.description,
minetest.colorize("#dbc217", "Mag type: " .. def.mag_type),
}
minetest.register_tool(name, {
description = table.concat(descrip, "\n"),
inventory_image = def.inventory_image,
wield_scale = def.wield_scale or nil,
--[[We set range to 0 so that the gun cant interact with
nodes when a user tries to ADS (Aim Down Sights)]]
range = 0,
--Fields defined by the api
fire = fire,
alt_fire = def.alt_fire or nil,
mag_type = def.mag_type,
sounds = def.sounds or nil,
callbacks = calls or nil,
on_use = function(itemstack, user, pointed_thing)
--Get the guns meta
local meta = itemstack:get_meta()
--Get the itemdef of the wielded firearm
local gun = minetest.registered_items[name]
--Set the guns fire mode if not already done
if not meta:contains("mode") then
meta:set_string("mode", "fire")
end
--Check if gun is loaded
if not meta:contains("mag") and (not calls or not calls.on_load_mag
or calls.on_load_mag({user = user, itemstack = itemstack})) then
--[[Search through the players inv looking for the mag with the most ammo,
mag must have > 0 bullets]]
local upper, upper_bullets, index
for idx, stack in ipairs(user:get_inventory():get_list("main")) do
--Get stack meta
local stack_meta = stack:get_meta()
--Check if stack exists and if it 'contains bullets'
if not stack:is_empty() and stack_meta:contains("bullets") then
--Get meta field and itemdef
local bullets = stack_meta:get_int("bullets")
local itemdef = minetest.registered_items[stack:get_name()]
if bullets > 0 and itemdef.mag_type == gun.mag_type
and (not upper or bullets > upper_bullets) then
upper, upper_bullets, index = stack, bullets, idx
end
end
end
--Load the mag
if upper and upper_bullets and index then
if gun.sounds and gun.sounds.mag_load then
minetest.sound_play(gun.sounds.mag_load, {object = user})
end
meta:set_string("mag", upper:get_name() .. "," .. upper_bullets)
itemstack:set_wear(upper:get_wear())
user:get_inventory():set_stack("main", index, nil)
end
return itemstack
--Fire the gun
else
local temp = itemstack:get_meta():get_string("mag"):split(",")
local mag, ammo = temp[1], tonumber(temp[2])
if ammo > 0 then
if not gunkit.firing[user] then
--Add to firing queue
gunkit.firing[user] = {
stack = itemstack,
wield_index = user:get_wield_index(),
mag = {name = mag, ammo = ammo}
}
end
elseif gun.sounds and gun.sounds.fire_empty then
minetest.sound_play(gun.sounds.fire_empty, {object = user})
end
end
end,
on_secondary_use = function(itemstack, user, pointed_thing)
--Get the guns meta
local meta = itemstack:get_meta()
--Set the guns fire mode if not already done
if not meta:contains("mode") then
meta:set_string("mode", "fire")
end
--Check if gun is loaded
if meta:contains("mag") then
local temp = meta:get_string("mag"):split(",")
local mag, ammo = temp[1], tonumber(temp[2])
if ammo > 0 and not gunkit.firing[user] then
--Get mode def
local mode_def = minetest.registered_items[itemstack:get_name()][meta:get_string("mode")]
if user:get_fov() == 0 and mode_def.zoom and mode_def.zoom_time then
user:set_fov(1 / mode_def.zoom, true, mode_def.zoom_time)
end
--Add to firing queue
gunkit.firing[user] = {
stack = itemstack,
wield_index = user:get_wield_index(),
mag = {name = mag, ammo = ammo}
}
end
end
end,
on_drop = function(itemstack, dropper, pos)
local meta = itemstack:get_meta()
local wield = dropper:get_wield_index()
local inv = dropper:get_inventory()
local gun = minetest.registered_items[itemstack:get_name()]
--Check if gun is loaded
if meta:contains("mag") then
local temp = meta:get_string("mag"):split(",")
local mag, ammo = temp[1], tonumber(temp[2])
--Check if mag is empty
if ammo == 0 and (not calls or not calls.on_drop_mag
or calls.on_drop_mag({user = dropper, itemstack = itemstack})) then
if gun.sounds and gun.sounds.mag_drop then
minetest.sound_play(gun.sounds.mag_drop, {object = dropper})
end
local stack = ItemStack(mag)
stack:set_wear(65534)
if inv:room_for_item("main", stack) then
inv:add_item("main", stack)
else
minetest.item_drop(stack, dropper, pos)
end
itemstack:set_wear(0)
meta:set_string("mag", "")
--If not then swap fire mode
elseif meta:contains("mode") then
meta:set_string("mode", gunkit.swap_mode(dropper, gun, meta:get_string("mode")))
end
--Otherwise drop gun
else
minetest.item_drop(itemstack, dropper, pos)
end
inv:set_stack("main", wield, itemstack)
end,
})
end
function gunkit.register_mag(name, def)
--Fancy item descrips
local descrip = {
def.description,
minetest.colorize("#dbc217", "Mag type: " .. def.mag_type),
minetest.colorize("#40c3cb", "Ammo: " .. def.ammo),
}
minetest.register_tool(name, {
description = table.concat(descrip, "\n"),
inventory_image = def.inventory_image,
--Fields defined by the api
mag_type = def.mag_type,
ammo = def.ammo,
max_ammo = def.max_ammo,
})
minetest.register_craft({
type = "shapeless",
output = name,
recipe = {name, def.ammo},
})
minetest.register_craft({
type = "shapeless",
output = name .. " 1 65534",
recipe = {name},
})
minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
local mag, ammo, meta, count
--Check for corresponding mag and ammo
for idx, stack in pairs(old_craft_grid) do
--Get stack name
local stack_name = stack:get_name()
if mag and ammo then
break
elseif not mag and stack_name == name then
mag = stack
meta = mag:get_meta()
count = meta:get_int("bullets")
elseif not ammo and stack_name == def.ammo then
ammo = stack
end
end
--Empty mag
if mag and not ammo then
meta:set_int("bullets", 0)
craft_inv:add_item("craft", {name = def.ammo, count = count})
--Fill empty mag
elseif mag and ammo then
local bullets = ammo:get_count()
local needs = def.max_ammo - count
if needs == 0 then
craft_inv:add_item("craft", {name = def.ammo})
itemstack:get_meta():set_int("bullets", count)
return
else
local use
if needs > bullets then
use = bullets
else
use = needs
end
craft_inv:remove_item("craft", {name = def.ammo, count = use})
itemstack:get_meta():set_int("bullets", count + use)
itemstack:set_wear(65534 - (65534 / def.max_ammo * (count + use - 1)))
end
end
return itemstack
end)
end
--
-- Create some internal functions that we will call later
--
--This should be pretty obvious
function gunkit.swap_mode(user, def, mode)
local modes = {fire = "alt_fire", alt_fire = "fire"}
--Get alternate mode
local alt_mode = modes[mode]
--Check for alternate mode
if def[alt_mode] then
local sounds = def[mode].sounds
if sounds.fire_toggle then
minetest.sound_play(sounds.fire_toggle, {object = user})
end
return alt_mode
else
return mode
end
end
--[[get the end of a vector calculated from user pos, look dir, and item range.
best not to touch the math here, im not completely sure how it works either]]
function gunkit.get_vector(user, p_pos, def)
--Add eyeheight offset
p_pos.y = p_pos.y + user:get_properties().eye_height or 1.625
local cam = {
x = minetest.yaw_to_dir(user:get_look_horizontal()),
z = user:get_look_dir()
}
local dir = vector.multiply(minetest.yaw_to_dir(math.rad(1)), def.range)
local e_pos = vector.add(p_pos, vector.multiply(cam.z, dir.z))
e_pos = vector.add(e_pos, vector.multiply(cam.x, dir.x))
return p_pos, e_pos
end
--Handle weapon firing
function gunkit.fire(user, stack, mag, p_pos, e_pos)
local mode = stack:get_meta():get_string("mode")
local def = minetest.registered_items[stack:get_name()]
local mode_def = def[mode]
local shots = mode_def.shots
if shots > mag.ammo then
shots = mag.ammo
end
mag.ammo = math.max(0, mag.ammo - shots)
stack:set_wear(65534 - (65534 / minetest.registered_items[mag.name].max_ammo * mag.ammo))
--Get mode_def sounds table
local sounds = mode_def.sounds
--Check if table exists
if sounds then
if sounds.fire then
minetest.sound_play(sounds.fire, {pos = p_pos})
end
if sounds.shell_drop then
minetest.sound_play(sounds.shell_drop, {pos = p_pos})
end
end
for i = 1, shots do
--3d bullet spread (in degrees)
e_pos = vector.apply(e_pos, function(n)
return n + math.rad(get_random_float(-mode_def.spread, mode_def.spread)) * mode_def.range
end)
minetest.add_particle({
pos = p_pos,
velocity = vector.multiply(vector.direction(p_pos, e_pos), 80),
expirationtime = 3,
collisiondetection = true,
collision_removal = true,
size = 2,
texture = mode_def.bullet_texture,
})
for pointed_thing in minetest.raycast(p_pos, e_pos, true, false) do
if pointed_thing.type == "node" then
break
elseif pointed_thing.type == "object" and pointed_thing.ref ~= user then
local luaent = pointed_thing.ref:get_luaentity()
if not luaent or not luaent.name:find("builtin") then
local calls = def.callbacks
if not calls or not calls[mode] or not calls[mode].on_hit
or calls[mode].on_hit({itemstack = stack, user = user, obj = pointed_thing.ref}) then
pointed_thing.ref:punch(user, 1.0, {full_punch_interval = 1.0, damage_groups = {fleshy = mode_def.dmg}})
break
end
end
end
end
end
return mag
end
--
-- Globalstep to keep track of firing players and weapon cooldowns
--
local counter = 0
minetest.register_globalstep(
function(dtime)
local current = minetest.get_us_time() / 1000000
counter = counter + dtime
if counter > gunkit.count_interval then
--Check users gun cooldowns
for user, items in pairs(gunkit.timer) do
for item, modes in pairs(items) do
for mode, time in pairs(modes) do
if current - time > minetest.registered_items[item][mode].interval then
gunkit.timer[user][item][mode] = nil
end
end
if get_table_size(modes) == 0 then
gunkit.timer[user][item] = nil
end
end
if get_table_size(items) == 0 then
gunkit.timer[user] = nil
end
end
counter = 0
end
--Loop through currently firing users
for user, tbl in pairs(gunkit.firing) do
local def = minetest.registered_items[user:get_wielded_item():get_name()]
if def.name == tbl.stack:get_name() and user:get_wield_index() == tbl.wield_index then
local name = def.name
local meta = tbl.stack:get_meta()
local mode = meta:get_string("mode")
local mode_def = def[mode]
local keys = user:get_player_control()
if keys.LMB then
local timer = gunkit.timer[user]
local calls = def.callbacks
if (not timer or not timer[name] or not timer[name][mode] or current - timer[name][mode] > mode_def.interval)
and (not calls or not calls[mode] or not calls[mode].on_fire or calls[mode].on_fire({itemstack = tbl.stack, user = user})) then
if meta:contains("mag") and tbl.mag.ammo > 0 then
local p_pos, e_pos = gunkit.get_vector(user, user:get_pos(), mode_def)
minetest.after(mode_def.range / mode_def.speed, gunkit.fire, user, tbl.stack, tbl.mag, p_pos, e_pos)
gunkit.timer[user] = gunkit.timer[user] or {}
gunkit.timer[user][name] = gunkit.timer[user][name] or {}
gunkit.timer[user][name][mode] = current
end
end
end
local fov = user:get_fov()
local zoom = mode_def.zoom
local zoom_time = mode_def.zoom_time
if zoom and zoom_time then
if not keys.RMB and fov ~= 0 then
user:set_fov(0, true, zoom_time)
elseif keys.RMB and fov == 0 then
user:set_fov(1 / zoom, true, zoom_time)
end
end
if not keys.LMB and not keys.RMB then
gunkit.firing[user] = nil
meta:set_string("mag", tbl.mag.name .. "," .. tbl.mag.ammo)
user:set_wielded_item(tbl.stack)
end
else
gunkit.firing[user] = nil
tbl.stack:get_meta():set_string("mag", tbl.mag.name .. "," .. tbl.mag.ammo)
user:get_inventory():set_stack("main", tbl.wield_index, tbl.stack)
user:set_fov(0)
end
end
end
)

1
mods/gunkit/init.lua Normal file
View File

@ -0,0 +1 @@
dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/api.lua")

18
mods/gunkit/license.txt Normal file
View File

@ -0,0 +1,18 @@
Copyright 2020 Cahrs (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1
mods/gunkit/mod.conf Normal file
View File

@ -0,0 +1 @@
name = gunkit