Lua scripting

Money

Move money atomically with a stable transaction key, keep accounts for the player, and open loans with honest terms.

The player's money is the money stat, in US dollars. You can still change it with ModifyStat, but as soon as a mod moves money (pays a wage, sells something, keeps savings, lends), use the Ledger: it never pays twice, never loses money to the stat's cap, keeps an audit the player can read (the wallet on the map's rail), and keeps a mod's money reachable when the mod is turned off.

lualocal M = "ynl_Nightlife"

local r = Ledger:pay(M, "cover_" .. night, 15, "Cover charge")
if not r.ok then
	Game:Narrate(r.reason)            -- "Not enough cash."
end

Transfers

Ledger:transfer(mod, key, { from, to, amount, memo, partial }) moves amount dollars between:

  • "cash": the player's money;
  • "world": outside the game (a wage comes from it, a purchase goes to it);
  • an account of your mod, by name ("reserve", "tips"): money you keep for the player.

Ledger:pay(mod, key, amount, memo) is cash to the world, Ledger:receive(mod, key, amount, memo, partial) the world to cash.

  • All or nothing. If the source has too little, or the cash is at its cap, nothing moves and reason says why. With partial = true, what fits moves and amount says how much.
  • One key, once. The key names this movement ("wage_" .. week, "sale_" .. itemId). Using it again returns the first result with repeated = true and moves nothing, also after a load, so a handler that runs twice or a double click cannot pay twice. A refusal does not use the key: you can try again.
  • The result is { ok, amount, id, reason, repeated }.

Ledger:balance(mod, account), Ledger:cash(), Ledger:room() (how much more the cash can hold), Ledger:transaction(mod, key), Ledger:history([mod [, count]]).

Loans

lualocal r = Ledger:contract(M, "loan_" .. n, {
	lender = "Nightlife tab",
	principal = 500,       -- paid to cash now: all of it, or the contract is not opened
	owed = 550,            -- total to repay
	dueIn = 168,           -- or due = an absolute time
	lateFee = 25,          -- added once per lateEvery hours after the due time...
	maxLateFees = 1,       -- ...at most this many times
	lateEvery = 24,
})
Ledger:repay(M, "loan_" .. n, 100)   -- partial payments are fine
  • State the terms to the player before opening it; the wallet panel shows them as well (owed, due date, late fee and its limit).
  • Late fees are applied by the game on time, once each, also after a long sleep, up to maxLateFees. A reminder the day before and an overdue notice go to the player's notifications.
  • Ledger:getContract(mod, key) gives owed, paid, due, overdue, lateFeesApplied, state ("active", "repaid") and, once repaid, onTime.

Debts your mod sets the terms of

A loan shark's juice, a bookie's tab that grows ten per cent a week, a debt sold to someone worse: the contract can change after it is opened, and the wallet follows.

lua-- interest the game adds by itself, by game time, also across a long sleep
Ledger:contract(M, "shark_1", {
	lender = "Big Tony", principal = 1000, owed = 1000, dueIn = 168,
	interest = 0.1,             -- 10 %...
	interestEvery = 24,         -- ...a day, compounding
	interestWhenLate = true,    -- only once the due time has passed
	maxOwed = 5000,             -- never more than this
})

Ledger:adjust(M, "shark_1", 200, "Juice for the week", "shark_1_w" .. week)  -- once per week key
Ledger:adjust(M, "shark_1", -300, "He owed me a favour")                      -- 0 closes it
Ledger:setTerms(M, "shark_1", { lender = "The Kovac brothers", dueIn = 72 })  -- sold on
  • adjust adds to what is owed or takes it off; it never pays or takes cash (use repay and pay for money). Reaching 0 closes the contract as repaid with nothing paid, and the wallet says so.
  • Its last argument is a once key: the same key twice changes the debt once, like a transaction key.
  • setTerms changes only the fields you give. A new due date gets a new reminder; a new lender is logged as "debt passed on".
  • A contract with no due date (due = 0) is simply owed until your mod closes it: no reminder, no late fee.
  • Every interest period, adjustment and change of lender is in the ledger's history and the wallet's recent list.

If your mod already keeps its debts in its own variables, it can keep its rules and mirror them: open each debt with migrate = true and principal = 0, then adjust the contract by the difference whenever your own number changes (ul_Debts does this every hour and after each action). A debt cleared and taken again needs a new key: a repaid contract stays in the history.

When your mod is turned off

Your accounts stay the player's: the wallet panel lists them and offers to move each balance back to cash. Your contracts are frozen (no fees while the mod is off) and listed with their terms. Nothing is forgiven and nothing is taken.

Moving your own saved money in

A mod that kept balances in its own variables moves them once:

  • Ledger:openAccount(mod, account, balance, memo) opens an account with that balance (it does not take it from cash: it already left the cash when it was saved). It does nothing if the account exists.
  • Ledger:contract(mod, key, { ..., migrate = true, lateFeesApplied = n, created = time }) records a loan as it stands, without paying the principal again or re-applying a fee that was already added.

Clear your variables after moving them; see ul_Finance's F.migrate.

Showing amounts

Ledger:format(dollars [, cents]) gives the amount in the city's currency, "£1,250" or "£12.50", with the same rate as Game:convertToLocalCurrency. Keep every amount you store in dollars.