193 lines
7.5 KiB
Lua
193 lines
7.5 KiB
Lua
--[[
|
|
Core API and Data Handling
|
|
--------------------------
|
|
This file manages all low-level reading from and writing to the 'accounts' data file.
|
|
It provides a safe and centralized API for all other mod files to interact with player data.
|
|
--]]
|
|
|
|
local world_path = minetest.get_worldpath()
|
|
local file_path = world_path .. "/accounts"
|
|
|
|
-- Normalizes a string to use '.' as a decimal separator and converts to a number.
|
|
-- Handles both "1,23" and "1.23".
|
|
function normalize_and_tonumber(str)
|
|
if not str then
|
|
return nil
|
|
end
|
|
str = str:gsub(",", ".")
|
|
return tonumber(str)
|
|
end
|
|
|
|
-- Internal function to read the entire accounts database file.
|
|
-- Returns a populated table or a clean default structure on any error.
|
|
local function read_accounts_file()
|
|
local f = io.open(file_path, "r")
|
|
if not f then
|
|
return { balance = {}, pin = {}, credit = {}, history = {} }
|
|
end
|
|
local data = f:read("*a")
|
|
f:close()
|
|
if data == "" or data == nil then
|
|
return { balance = {}, pin = {}, credit = {}, history = {} }
|
|
end
|
|
return minetest.deserialize(data) or { balance = {}, pin = {}, credit = {}, history = {} }
|
|
end
|
|
|
|
-- Internal function to write the entire accounts database file.
|
|
-- Includes a locking mechanism to prevent race conditions with the interest script.
|
|
local function save_accounts_file(data)
|
|
-- If the daily interest calculation is running, wait in short intervals until it's done.
|
|
while bank_accounts and bank_accounts.is_calculating_interest do
|
|
minetest.log("action", "[bank_accounts] Interest calculation in progress, delaying save operation...")
|
|
minetest.sleep(0.5)
|
|
end
|
|
|
|
local f, err = io.open(file_path, "w")
|
|
if not f then
|
|
minetest.log("error", "[bank_accounts] Could not open accounts file for writing: " .. tostring(err))
|
|
return false
|
|
end
|
|
f:write(minetest.serialize(data))
|
|
f:close()
|
|
return true
|
|
end
|
|
|
|
-- Internal function to log a single transaction to a player's history.
|
|
local function log_transaction(data, player_name, account_type, amount, new_total, trans_type, purpose, other_party)
|
|
-- Ensure history tables exist.
|
|
if not data.history then data.history = {} end
|
|
if not data.history[player_name] then data.history[player_name] = {} end
|
|
|
|
local transaction = {
|
|
timestamp = os.time(),
|
|
type = trans_type or "unknown",
|
|
account = account_type, -- "balance" or "credit"
|
|
amount = amount,
|
|
new_total = new_total,
|
|
purpose = purpose or "", -- e.g., "Teller: Rage87" or user-defined text
|
|
other = other_party or "" -- e.g., recipient or sender name
|
|
}
|
|
-- Insert at the beginning of the list to show newest transactions first.
|
|
table.insert(data.history[player_name], 1, transaction)
|
|
|
|
-- Limit history to the last 200 entries to prevent the file from growing indefinitely.
|
|
while #data.history[player_name] > 200 do
|
|
table.remove(data.history[player_name])
|
|
end
|
|
end
|
|
|
|
-- On server start, create the accounts file if it doesn't exist.
|
|
do
|
|
local f = io.open(file_path, "r")
|
|
if not f then
|
|
save_accounts_file({ balance = {}, pin = {}, credit = {}, history = {} })
|
|
else
|
|
f:close()
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------
|
|
-- Public API accessible via `bank_accounts.*`
|
|
---------------------------------------------------
|
|
|
|
-- Gets all relevant data for a single player.
|
|
function bank_accounts.get_account_data(player_name)
|
|
local data = read_accounts_file()
|
|
-- Backwards compatibility check for old save files without a history table.
|
|
if not data.history then
|
|
data.history = {}
|
|
end
|
|
return {
|
|
balance = data.balance[player_name] or 0,
|
|
pin = data.pin[player_name] or "0000",
|
|
credit = data.credit[player_name] or 0,
|
|
history = data.history[player_name] or {},
|
|
}
|
|
end
|
|
|
|
-- Gets all data from the database (for background scripts like interest calculation).
|
|
function bank_accounts.get_all_data()
|
|
return read_accounts_file()
|
|
end
|
|
|
|
-- Saves the complete data object (for background scripts).
|
|
function bank_accounts.save_all(data)
|
|
return save_accounts_file(data)
|
|
end
|
|
|
|
-- Simple getter functions.
|
|
function bank_accounts.get_balance(player_name) local data = read_accounts_file(); return data.balance[player_name] or 0 end
|
|
function bank_accounts.get_credit(player_name) local data = read_accounts_file(); return data.credit[player_name] or 0 end
|
|
function bank_accounts.get_pin(player_name) local data = read_accounts_file(); return data.pin[player_name] or "0000" end
|
|
|
|
-- Adds a value to a player's balance and logs the transaction.
|
|
function bank_accounts.add_balance(player_name, amount, trans_type, purpose, other_party)
|
|
local data = read_accounts_file()
|
|
local current_balance = data.balance[player_name] or 0
|
|
local new_balance = current_balance + tonumber(amount)
|
|
data.balance[player_name] = new_balance
|
|
log_transaction(data, player_name, "balance", amount, new_balance, trans_type, purpose, other_party)
|
|
return save_accounts_file(data)
|
|
end
|
|
|
|
-- Sets a player's balance to an absolute value and logs the transaction.
|
|
function bank_accounts.set_balance(player_name, amount, trans_type, purpose, other_party)
|
|
local data = read_accounts_file()
|
|
local current_balance = data.balance[player_name] or 0
|
|
local new_balance = tonumber(amount)
|
|
local diff = new_balance - current_balance
|
|
data.balance[player_name] = new_balance
|
|
log_transaction(data, player_name, "balance", diff, new_balance, trans_type or "admin_set", purpose, other_party)
|
|
return save_accounts_file(data)
|
|
end
|
|
|
|
-- Adds a value to a player's credit debt and logs the transaction.
|
|
function bank_accounts.add_credit(player_name, amount, trans_type, purpose, other_party)
|
|
local data = read_accounts_file()
|
|
local current_credit = data.credit[player_name] or 0
|
|
local new_credit = current_credit + tonumber(amount)
|
|
data.credit[player_name] = new_credit
|
|
log_transaction(data, player_name, "credit", amount, new_credit, trans_type, purpose, other_party)
|
|
return save_accounts_file(data)
|
|
end
|
|
|
|
-- Sets a player's credit debt to an absolute value and logs the transaction.
|
|
function bank_accounts.set_credit(player_name, amount, trans_type, purpose, other_party)
|
|
local data = read_accounts_file()
|
|
local current_credit = data.credit[player_name] or 0
|
|
local new_credit = tonumber(amount)
|
|
local diff = new_credit - current_credit
|
|
data.credit[player_name] = new_credit
|
|
log_transaction(data, player_name, "credit", diff, new_credit, trans_type or "admin_set_credit", purpose, other_party)
|
|
return save_accounts_file(data)
|
|
end
|
|
|
|
-- Sets a player's PIN.
|
|
function bank_accounts.set_pin(player_name, pin)
|
|
local data = read_accounts_file()
|
|
data.pin[player_name] = tostring(pin)
|
|
return save_accounts_file(data)
|
|
end
|
|
|
|
-- Checks if a player has an entry in the database.
|
|
function bank_accounts.player_has_account(player_name)
|
|
local data = read_accounts_file()
|
|
return data.balance[player_name] ~= nil
|
|
end
|
|
|
|
-- Creates a new, empty account for a player.
|
|
function bank_accounts.create_account(player_name)
|
|
if bank_accounts.player_has_account(player_name) then
|
|
return false
|
|
end
|
|
local data = read_accounts_file()
|
|
data.balance[player_name] = 0
|
|
data.pin[player_name] = "0000"
|
|
data.credit[player_name] = 0
|
|
if not data.history then
|
|
data.history = {}
|
|
end
|
|
data.history[player_name] = {}
|
|
log_transaction(data, player_name, "system", 0, 0, "Account Created", "Initial account setup")
|
|
return save_accounts_file(data)
|
|
end
|