etlegacy-lua-scripts/xpsave/xpsave.lua
IR4T4 19185cd076 Update xpsave.lua
Lua 5.1 crash fix (arithmetic on field 'CS_PLAYERS'  (a nil value))
2014-10-07 21:19:43 +02:00

198 lines
6.4 KiB
Lua
Executable file

--[[
Author: Jan Šimek [Radegast]
Version 0.2
License: MIT
Released on 23.11.2013
Website: http://www.etlegacy.com
Mod: intended for Legacy, but might also work in NoQuarter
Description: this script saves users' experience points into
a database and thus preserves them between connections
]]--
-- load sqlite driver (or mysql..)
local luasql = require "luasql.sqlite3"
local env -- environment object
local con -- database connection
local cur -- cursor
-- skill identifiers
local BATTLESENSE = 0
local ENGINEERING = 1
local MEDIC = 2
local FIELDOPS = 3
local LIGHTWEAPONS = 4
local HEAVYWEAPONS = 5
local COVERTOPS = 6
local skills = {}
skills[BATTLESENSE] = "Battlesense"
skills[ENGINEERING] = "Engineering"
skills[MEDIC] = "Medic"
skills[FIELDOPS] = "Field ops"
skills[LIGHTWEAPONS] = "Light weapons"
skills[HEAVYWEAPONS] = "Heavy weapons"
skills[COVERTOPS] = "Covert ops"
-- database helper function
-- returns database rows matching sql_statement
function rows(connection, sql_statement)
local cursor = assert (connection:execute (sql_statement))
return function ()
return cursor:fetch()
end
end -- rows
-- con:prepare with bind_names should be used to prevent sql injections
-- but it doesn't work on my version of luasql
function validateGUID(cno, guid)
-- allow only alphanumeric characters in guid
if(string.match(guid, "%W")) then
-- Invalid characters detected. We should probably drop this client
et.G_Print("^3WARNING: (XP Save) user with id " .. cno .. " has an invalid GUID: " .. guid .. "\n")
et.trap_SendServerCommand (cno, "cpm \"" .. "Your XP won't be saved because you have an invalid cl_guid.\n\"")
return false
end
return true
end
-- saves XP values of a player with id 'cno' into sqlite database
function saveXP(cno)
local name = et.Info_ValueForKey( et.trap_GetUserinfo( cno ), "name" )
local guid = et.Info_ValueForKey( et.trap_GetUserinfo( cno ), "cl_guid" )
if not validateGUID(cno, guid) then return end
cur = assert (con:execute(string.format("SELECT * FROM users WHERE guid='%s' LIMIT 1", guid)))
local player = cur:fetch({}, 'a')
if not player then
-- This should not happen
et.G_Print ("^1ERROR: (XP Save) user was not found in the database!\n")
return
else
et.trap_SendServerCommand (cno, "cpm \"" .. "See you again soon, " .. name .. "\n\"")
--for id, name in pairs(skills) do et.G_Print (name .. ": " .. et.gentity_get (cno, "sess.skillpoints", id) .. " XP\n") end
cur = assert (con:execute(string.format([[UPDATE users SET
last_seen='%s',
xp_battlesense='%s',
xp_engineering='%s',
xp_medic='%s',
xp_fieldops='%s',
xp_lightweapons='%s',
xp_heavyweapons='%s',
xp_covertops='%s'
WHERE guid='%s']],
os.date("%Y-%m-%d %H:%M:%S"),
et.gentity_get (cno, "sess.skillpoints", BATTLESENSE),
et.gentity_get (cno, "sess.skillpoints", ENGINEERING),
et.gentity_get (cno, "sess.skillpoints", MEDIC),
et.gentity_get (cno, "sess.skillpoints", FIELDOPS),
et.gentity_get (cno, "sess.skillpoints", LIGHTWEAPONS),
et.gentity_get (cno, "sess.skillpoints", HEAVYWEAPONS),
et.gentity_get (cno, "sess.skillpoints", COVERTOPS),
guid
)))
end
end
-- init db on game start
function et_InitGame(levelTime, randomSeed, restart)
-- name of this module
et.RegisterModname ( "XP Save player database" )
-- create environement object
env = assert ( luasql.sqlite3() )
-- connect to database
con = assert ( env:connect( "xpsave.sqlite" ) )
--cur = assert (con:execute( "DROP TABLE users" ))
cur = assert ( con:execute[[
CREATE TABLE IF NOT EXISTS users(
guid VARCHAR(64),
last_seen VARCHAR(64),
xp_battlesense REAL,
xp_engineering REAL,
xp_medic REAL,
xp_fieldops REAL,
xp_lightweapons REAL,
xp_heavyweapons REAL,
xp_covertops REAL,
UNIQUE (guid)
)
]])
cur = assert ( con:execute("SELECT COUNT(*) FROM users") )
et.G_Print("There are " .. tonumber(cur:fetch(row, 'a')) .. " users in the XP save database.\n")
--et.G_Print ("^4List of users in XP Save database:\n")
--for guid, date in rows (con, "SELECT * FROM users") do
-- et.G_Print (string.format ("\tGUID %s was last seen on %s\n", guid, date))
--end
end -- et_InitGame
function et_ShutdownGame(restart)
local cno = 0
local maxclients = tonumber(et.trap_Cvar_Get("sv_maxclients"))
-- iterate through clients and save their XP
while cno < maxclients do
local cs = et.trap_GetConfigstring(tonumber(et.CS_PLAYERS) + cno)
if not cs or cs == "" then break end
saveXP(cno)
cno = cno + 1
end
-- clean up
cur:close()
con:close()
env:close()
end -- et_ShutdownGame
-- called when a client enters the game world
function et_ClientBegin(cno)
local name = et.Info_ValueForKey( et.trap_GetUserinfo( cno ), "name" )
local guid = et.Info_ValueForKey( et.trap_GetUserinfo( cno ), "cl_guid" )
if not validateGUID(cno, guid) then return end
cur = assert (con:execute(string.format("SELECT * FROM users WHERE guid='%s'", guid)))
local player = cur:fetch({}, 'a')
if not player then
-- First time we see this player
et.trap_SendServerCommand (cno, "cpm \"" .. "Welcome, " .. name .. "^7! You are playing on an XP save server.\n\"")
cur = assert (con:execute(string.format("INSERT INTO users VALUES ('%s', '%s', 0, 0, 0, 0, 0, 0, 0)", guid, os.date("%Y-%m-%d %H:%M:%S"))))
else
et.trap_SendServerCommand (cno, "cpm \"" .. "Welcome back, " .. name .. "^7! Your last connection was on " .. player.last_seen .. "\n\"") -- in db: player.name
--et.G_Print ("Loading XP from database: " .. player.xp_battlesense .. " | " .. player.xp_engineering .. " | " .. player.xp_medic .. " | " .. player.xp_fieldops .. " | " .. player.xp_lightweapons .. " | " .. player.xp_heavyweapons .. " | " .. player.xp_covertops .. "\n\n")
et.G_XP_Set (cno, player.xp_battlesense, BATTLESENSE, 0)
et.G_XP_Set (cno, player.xp_engineering, ENGINEERING, 0)
et.G_XP_Set (cno, player.xp_medic, MEDIC, 0)
et.G_XP_Set (cno, player.xp_fieldops, FIELDOPS, 0)
et.G_XP_Set (cno, player.xp_lightweapons, LIGHTWEAPONS, 0)
et.G_XP_Set (cno, player.xp_heavyweapons, HEAVYWEAPONS, 0)
et.G_XP_Set (cno, player.xp_covertops, COVERTOPS, 0)
et.G_Print (name .. "'s current XP levels:\n")
for id, skill in pairs(skills) do
et.G_Print ("\t" .. skill .. ": " .. et.gentity_get (cno, "sess.skillpoints", id) .. " XP\n")
end
end
end -- et_ClientBegin
function et_ClientDisconnect(cno)
saveXP(cno)
end -- et_ClientDisconnect