bank_accounts/functions.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