insidethebox/mods/db/init.lua

284 lines
7.1 KiB
Lua

--[[
sqlite backend for insidethebox
This backend stores various persistent world data bits that are
not properly stored by the minetest engine. This mod provides both
persistent and performant data storage for the itb game until a
better solution exists.
Major data storage parts in this db:
- players, player scores
No player metadata storage exists currently. Only HP and air
are stored in player data. We lack storage for things like
skin choice, stamina, RPG points and skills, etc.
- box data
--]]
-- gratuitous banner
minetest.log("action", " _|_|_| _| _|")
minetest.log("action", " _| _|_|_| _|_|_| _|_|_| _|_|")
minetest.log("action", " _| _| _| _|_| _| _| _| _|_|_|_|")
minetest.log("action", " _| _| _| _|_| _| _| _| _|")
minetest.log("action", " _|_|_| _| _| _|_|_| _| _|_|_| _|_|_|")
minetest.log("action", "")
minetest.log("action", " _| _| _|")
minetest.log("action", " _|_|_|_| _|_|_| _|_| _|_|_| _|_| _| _|")
minetest.log("action", " _| _| _| _|_|_|_| _| _| _| _| _|_|")
minetest.log("action", " _| _| _| _| _| _| _| _| _| _|")
minetest.log("action", " _|_| _| _| _|_|_| _|_|_| _|_| _| _|")
local world_path = minetest.get_worldpath()
-- local sqlite3 = require("lsqlite3")
local insecure_env = minetest.request_insecure_environment()
local sqlite3 = insecure_env.require("lsqlite3")
local itb_db = sqlite3.open(world_path .. "/itb.sqlite")
if not itb_db then
assert("lsqlite3 not available or non-functional. Please use e.g. luarocks to install lsqlite3")
end
-- we require these tables to exist:
assert(itb_db:exec[[
CREATE TABLE box (id INTEGER PRIMARY KEY, data BLOB);
CREATE TABLE box_meta (id INTEGER PRIMARY KEY, type INTEGER, meta BLOB);
CREATE TABLE series (id INTEGER PRIMARY KEY, name TEXT, meta BLOB);
CREATE TABLE series_box (series_id INTEGER, box_id INTEGER, box_order INTEGER);
CREATE TABLE player (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, meta BLOB);
CREATE TABLE points (player_id INTEGER, score REAL, type TEXT, box_id INTEGER);
]])
-- table structure and content
--[[
box table: Contain the storage of the box content (not metadata) so all blocks. (one-to-one)
- id: box identifier
- data: box contents (nodes)
series: Contains existing series
- id: series ID
- name: name of the series
- meta: misc data
series_box: maps boxes into series (many-to-many)
- series_id: series ID
- box_id: box ID
- box_order: series are sorted by increasing order
player: player info
- id: player number
- name: player name
- meta: random player data bits
score: player and box score data
- player_id: player id
- score: score value
- type: score type (e.g. reviewed a box)
- box: associated box ID
--]]
db = {}
function db.player_get_meta(name)
local stmt = itb_db:prepare[[
SELECT meta FROM player WHERE name = :name
]]
stmt:bind_names{name = name}
local it, state = stmt:nrows()
local row = it(state)
stmt:finalize()
if row then
local meta = minetest.parse_json(row.meta)
if not meta.series_progress then
meta.series_progress = {}
end
return meta
end
print("no such player in db", name)
return {series_progress = {}}
end
function db.player_set_meta(name, meta)
local stmt = itb_db:prepare[[
REPLACE INTO player (name, meta) VALUES (:name, :meta)
]]
stmt:bind_names{name = name, meta = minetest.write_json(meta)}
local r = stmt:step()
stmt:finalize()
if not r then
print("error writing")
return false
else
return true
end
end
function db.series_get_meta(series_id)
local stmt = itb_db:prepare[[
SELECT * FROM series WHERE id = :series_id
]]
stmt:bind_names{series_id = series_id}
local it, state = stmt:nrows()
local row = it(state)
stmt:finalize()
if row then
local result = {
name = row.name,
meta = minetest.parse_json(row.meta),
}
return result
end
print("no such series meta in db", series_id)
return nil
end
function db.series_set_meta(series_id, meta)
local stmt = itb_db:prepare[[
REPLACE INTO series (id, name, meta) VALUES(:series_id, :name, :meta)
]]
stmt:bind_names{
series_id = series_id,
name = meta.name,
meta = minetest.write_json(meta.meta),
}
local r = stmt:step()
stmt:finalize()
if not r then
print("error writing")
return false
else
return true
end
end
function db.series_get_boxes(series_id)
local stmt = itb_db:prepare[[
SELECT * FROM series_box WHERE series_id = :series_id ORDER BY box_order
]]
stmt:bind_names{series_id = series_id}
local boxes = {}
local boxes_index = 1
for row in stmt:nrows() do
boxes[boxes_index] = row.box_id
boxes_index = boxes_index + 1
end
stmt:finalize()
return boxes
end
function db.series_insert_box(series_id, box_id, box_order)
local stmt = itb_db:prepare[[
INSERT INTO series_box (series_id, box_id, box_order) VALUES(:series_id, :box_id, :box_order)
]]
stmt:bind_names{
series_id = series_id,
box_id = box_id,
box_order = box_order,
}
local r = stmt:step()
stmt:finalize()
if not r then
print("error writing")
return false
else
return true
end
end
function db.box_get_data(box_id)
local stmt = itb_db:prepare[[
SELECT data FROM box WHERE id = :box_id
]]
stmt:bind_names{box_id = box_id}
local it, state = stmt:nrows()
local row = it(state)
stmt:finalize()
if row then
return row.data
end
print("no such box in db", box_id)
return nil
end
function db.box_set_data(box_id, data)
local stmt = itb_db:prepare[[
REPLACE INTO box (id, data) VALUES(:box_id, :box_data)
]]
stmt:bind_names{box_id = box_id, box_data = data}
local r = stmt:step()
stmt:finalize()
if not r then
print("error writing")
return false
else
return true
end
end
db.BOX_TYPE = 0
db.ENTRY_TYPE = 1
db.EXIT_TYPE = 2
function db.box_get_meta(box_id)
local stmt = itb_db:prepare[[
SELECT * FROM box_meta WHERE id = :box_id
]]
stmt:bind_names{box_id = box_id}
local it, state = stmt:nrows()
local row = it(state)
stmt:finalize()
if row then
local result = {
type = row.type,
meta = minetest.parse_json(row.meta),
}
if result.type == db.BOX_TYPE and not result.meta.num_items then
result.meta.num_items = 1
end
if result.type == db.BOX_TYPE and not result.meta.num_completed_players then
result.meta.player_best_time = {}
result.meta.num_completed_players = 0
result.meta.average_first_time = 0
result.meta.average_best_time = 0
result.meta.best_time = 1e20 -- that's a few billion years
end
return result
end
print("no such box meta in db", box_id)
return nil
end
function db.box_set_meta(box_id, meta)
local stmt = itb_db:prepare[[
REPLACE INTO box_meta (id, type, meta) VALUES(:box_id, :box_type, :box_meta)
]]
stmt:bind_names{
box_id = box_id,
box_type = meta.type,
box_meta = minetest.write_json(meta.meta),
}
local r = stmt:step()
stmt:finalize()
if not r then
print("error writing")
return false
else
return true
end
end
function db.shutdown()
itb_db:close()
end