char logic change, huge db optimization

This commit is contained in:
Sky Johnson 2024-10-04 12:06:21 -05:00
parent afd565a507
commit 9da77307af
18 changed files with 202 additions and 62 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -28,7 +28,7 @@ if (!isset($argv[1])) {
} }
// make sure it's a valid database // make sure it's a valid database
if (!in_array($argv[1], [AUTH, LIVE, FIGHTS, BPS])) { if (!in_array($argv[1], [AUTH, LIVE, FIGHTS, BPS, 'reset'])) {
eln(red('Invalid database: ') . $argv[1]); eln(red('Invalid database: ') . $argv[1]);
exit(1); exit(1);
} }
@ -47,8 +47,8 @@ $drop = isset($argv[2]) && $argv[2] === '-d';
The Auth database is used to store user information - not player info, but user info. The Auth database is used to store user information - not player info, but user info.
Usernames, passwords, email, session tokens, etc. Usernames, passwords, email, session tokens, etc.
*/ */
if ($database === AUTH) { if ($database === AUTH || $database === 'reset') {
if ($drop) { if ($drop || $database === 'reset') {
unlink(__DIR__ . '/../' . AUTH); unlink(__DIR__ . '/../' . AUTH);
eln(red('Dropped database: ') . 'auth.db'); eln(red('Dropped database: ') . 'auth.db');
} }
@ -76,6 +76,7 @@ if ($database === AUTH) {
token TEXT NOT NULL UNIQUE, token TEXT NOT NULL UNIQUE,
expires INTEGER NOT NULL expires INTEGER NOT NULL
)'); )');
$db->exec('CREATE INDEX idx_sessions_user_id ON sessions (user_id)');
created_or_error($r, 'sessions'); created_or_error($r, 'sessions');
@ -86,20 +87,21 @@ if ($database === AUTH) {
token TEXT NOT NULL UNIQUE, token TEXT NOT NULL UNIQUE,
created INTEGER NOT NULL created INTEGER NOT NULL
)'); )');
$db->exec('CREATE INDEX idx_tokens_user_id ON tokens (user_id)');
created_or_error($r, 'tokens'); created_or_error($r, 'tokens');
eln(green('Created database: ') . 'auth.db'); eln(green('Created database: ') . 'auth.db');
exit(0); if ($database !== 'reset') exit(0);
} }
/* /*
The Fights database is used to store information about fights. The Fights database is used to store information about fights.
A fight is a battle between two characters; players or NPCs. A fight is a battle between two characters; players or NPCs.
*/ */
if ($database === FIGHTS) { if ($database === FIGHTS || $database === 'reset') {
if ($drop) { if ($drop || $database === 'reset') {
unlink(__DIR__ . '/../' . FIGHTS); unlink(__DIR__ . '/../' . FIGHTS);
eln(red('Dropped database: ') . 'fights.db'); eln(red('Dropped database: ') . 'fights.db');
} }
@ -148,6 +150,8 @@ if ($database === FIGHTS) {
created DATETIME DEFAULT CURRENT_TIMESTAMP, created DATETIME DEFAULT CURRENT_TIMESTAMP,
updated DATETIME DEFAULT CURRENT_TIMESTAMP updated DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// create an index for char_id
$db->exec('CREATE INDEX idx_pve_char_id ON pve (char_id)');
created_or_error($r, 'pve'); created_or_error($r, 'pve');
@ -191,6 +195,10 @@ if ($database === FIGHTS) {
created DATETIME DEFAULT CURRENT_TIMESTAMP, created DATETIME DEFAULT CURRENT_TIMESTAMP,
updated DATETIME DEFAULT CURRENT_TIMESTAMP updated DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// Create an index for char1_id
$db->exec('CREATE INDEX idx_pvp_char1_id ON pvp (char1_id)');
// Create an index for char2_id
$db->exec('CREATE INDEX idx_pvp_char2_id ON pvp (char2_id)');
created_or_error($r, 'pvp'); created_or_error($r, 'pvp');
@ -201,6 +209,8 @@ if ($database === FIGHTS) {
fight_id INTEGER NOT NULL, fight_id INTEGER NOT NULL,
info TEXT NOT NULL info TEXT NOT NULL
)'); )');
// Create an index for fight_id
$db->exec('CREATE INDEX idx_pve_logs_fight_id ON pve_logs (fight_id)');
created_or_error($r, 'pve_logs'); created_or_error($r, 'pve_logs');
@ -211,19 +221,21 @@ if ($database === FIGHTS) {
fight_id INTEGER NOT NULL, fight_id INTEGER NOT NULL,
info TEXT NOT NULL info TEXT NOT NULL
)'); )');
// Create an index for fight_id
$db->exec('CREATE INDEX idx_pvp_logs_fight_id ON pvp_logs (fight_id)');
created_or_error($r, 'pvp_logs'); created_or_error($r, 'pvp_logs');
eln(green('Created database: ') . 'fights.db'); eln(green('Created database: ') . 'fights.db');
exit(0); if ($database !== 'reset') exit(0);
} }
/* /*
The Blueprints database is used to store information about items, weapons, armor, etc. The Blueprints database is used to store information about items, weapons, armor, etc.
*/ */
if ($database === BPS) { if ($database === BPS || $database === 'reset') {
if ($drop) { if ($drop || $database === 'reset') {
unlink(__DIR__ . '/../' . BPS); unlink(__DIR__ . '/../' . BPS);
eln(red('Dropped database: ') . 'blueprints.db'); eln(red('Dropped database: ') . 'blueprints.db');
} }
@ -294,20 +306,38 @@ if ($database === BPS) {
eln(green('Created database: ') . 'blueprints.db'); eln(green('Created database: ') . 'blueprints.db');
exit(0); if ($database !== 'reset') exit(0);
} }
/* /*
The Live database is used to store information about players, NPCs, guilds, etc. The Live database is used to store information about players, NPCs, guilds, etc.
*/ */
if ($database === LIVE) { if ($database === LIVE || $database === 'reset') {
if ($drop) { if ($drop || $database === 'reset') {
unlink(__DIR__ . '/../' . LIVE); unlink(__DIR__ . '/../' . LIVE);
eln(red('Dropped database: ') . 'live.db'); eln(red('Dropped database: ') . 'live.db');
} }
$db = new SQLite3(__DIR__ . '/../' . LIVE); $db = new SQLite3(__DIR__ . '/../' . LIVE);
// Blog posts
$db->exec('DROP TABLE IF EXISTS blog');
$r = $db->exec('CREATE TABLE blog (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL,
title TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
content TEXT NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP,
updated DATETIME DEFAULT CURRENT_TIMESTAMP
)');
// Create an index for author_id
$db->exec('CREATE INDEX idx_blog_author_id ON blog (author_id)');
// Create an index for the slug
$db->exec('CREATE INDEX idx_blog_slug ON blog (slug)');
created_or_error($r, 'blog');
// Characters // Characters
$db->exec('DROP TABLE IF EXISTS characters'); $db->exec('DROP TABLE IF EXISTS characters');
$r = $db->exec('CREATE TABLE characters ( $r = $db->exec('CREATE TABLE characters (
@ -322,8 +352,8 @@ if ($database === LIVE) {
max_hp INTEGER NOT NULL DEFAULT 20, max_hp INTEGER NOT NULL DEFAULT 20,
current_mp INTEGER NOT NULL DEFAULT 10, current_mp INTEGER NOT NULL DEFAULT 10,
max_mp INTEGER NOT NULL DEFAULT 10, max_mp INTEGER NOT NULL DEFAULT 10,
current_tp INTEGER NOT NULL DEFAULT 0, current_tp INTEGER NOT NULL DEFAULT 1,
max_tp INTEGER NOT NULL DEFAULT 0, max_tp INTEGER NOT NULL DEFAULT 1,
power INTEGER NOT NULL DEFAULT 0, power INTEGER NOT NULL DEFAULT 0,
accuracy INTEGER NOT NULL DEFAULT 0, accuracy INTEGER NOT NULL DEFAULT 0,
penetration INTEGER NOT NULL DEFAULT 0, penetration INTEGER NOT NULL DEFAULT 0,
@ -337,6 +367,8 @@ if ($database === LIVE) {
inv_slots INTEGER NOT NULL DEFAULT 10, inv_slots INTEGER NOT NULL DEFAULT 10,
attrib_points INTEGER NOT NULL DEFAULT 0 attrib_points INTEGER NOT NULL DEFAULT 0
)'); )');
// Create an index for user_id
$db->exec('CREATE INDEX idx_characters_user_id ON characters (user_id)');
created_or_error($r, 'characters'); created_or_error($r, 'characters');
@ -367,6 +399,8 @@ if ($database === LIVE) {
max_hp INTEGER NOT NULL DEFAULT 0, max_hp INTEGER NOT NULL DEFAULT 0,
max_mp INTEGER NOT NULL DEFAULT 0 max_mp INTEGER NOT NULL DEFAULT 0
)'); )');
// Create an index for char_id
$db->exec('CREATE INDEX idx_char_gear_char_id ON char_gear (char_id)');
created_or_error($r, 'char_gear'); created_or_error($r, 'char_gear');
@ -376,18 +410,22 @@ if ($database === LIVE) {
char_id INTEGER NOT NULL, char_id INTEGER NOT NULL,
item_id INTEGER NOT NULL item_id INTEGER NOT NULL
)'); )');
// Create an index for char_id
$db->exec('CREATE INDEX idx_inventory_char_id ON inventory (char_id)');
created_or_error($r, 'inventory'); created_or_error($r, 'inventory');
// Player wallet // Player wallet
$db->exec('DROP TABLE IF EXISTS char_wallets'); $db->exec('DROP TABLE IF EXISTS wallets');
$r = $db->exec('CREATE TABLE char_wallets ( $r = $db->exec('CREATE TABLE wallets (
char_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
silver INTEGER NOT NULL DEFAULT 10, silver INTEGER NOT NULL DEFAULT 10,
stargem INTEGER NOT NULL DEFAULT 0 stargem INTEGER NOT NULL DEFAULT 0
)'); )');
// Create an index for user_id
$db->exec('CREATE INDEX idx_wallets_user_id ON wallets (user_id)');
created_or_error($r, 'char_wallets'); created_or_error($r, 'wallets');
// Player bank // Player bank
$db->exec('DROP TABLE IF EXISTS char_bank'); $db->exec('DROP TABLE IF EXISTS char_bank');
@ -399,6 +437,8 @@ if ($database === LIVE) {
can_collect INTEGER NOT NULL DEFAULT 1, can_collect INTEGER NOT NULL DEFAULT 1,
last_collected DATETIME DEFAULT CURRENT_TIMESTAMP last_collected DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// Create an index for char_id
$db->exec('CREATE INDEX idx_bank_char_id ON bank (char_id)');
created_or_error($r, 'bank'); created_or_error($r, 'bank');
@ -408,6 +448,10 @@ if ($database === LIVE) {
char_id INTEGER NOT NULL, char_id INTEGER NOT NULL,
item_id INTEGER NOT NULL item_id INTEGER NOT NULL
)'); )');
// Create an index for char_id
$db->exec('CREATE INDEX idx_banked_items_char_id ON banked_items (char_id)');
// Create an index for item_id
$db->exec('CREATE INDEX idx_banked_items_item_id ON banked_items (item_id)');
created_or_error($r, 'banked_items'); created_or_error($r, 'banked_items');
@ -423,6 +467,8 @@ if ($database === LIVE) {
created DATETIME DEFAULT CURRENT_TIMESTAMP, created DATETIME DEFAULT CURRENT_TIMESTAMP,
updated DATETIME DEFAULT CURRENT_TIMESTAMP updated DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// Create an index for the x, y location
$db->exec('CREATE INDEX idx_towns_location ON towns (x, y)');
created_or_error($r, 'towns'); created_or_error($r, 'towns');
@ -443,6 +489,8 @@ if ($database === LIVE) {
created DATETIME DEFAULT CURRENT_TIMESTAMP, created DATETIME DEFAULT CURRENT_TIMESTAMP,
updated DATETIME DEFAULT CURRENT_TIMESTAMP updated DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// Create an index for the x, y location
$db->exec('CREATE INDEX idx_shops_location ON shops (x, y)');
created_or_error($r, 'shops'); created_or_error($r, 'shops');
@ -459,6 +507,8 @@ if ($database === LIVE) {
created DATETIME DEFAULT CURRENT_TIMESTAMP, created DATETIME DEFAULT CURRENT_TIMESTAMP,
updated DATETIME DEFAULT CURRENT_TIMESTAMP updated DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// Create an index for the x, y location
$db->exec('CREATE INDEX idx_inns_location ON inns (x, y)');
created_or_error($r, 'inns'); created_or_error($r, 'inns');
@ -474,6 +524,8 @@ if ($database === LIVE) {
created DATETIME DEFAULT CURRENT_TIMESTAMP, created DATETIME DEFAULT CURRENT_TIMESTAMP,
updated DATETIME DEFAULT CURRENT_TIMESTAMP updated DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// Create an index for leader_id
$db->exec('CREATE INDEX idx_guilds_leader_id ON guilds (leader_id)');
created_or_error($r, 'guilds'); created_or_error($r, 'guilds');
@ -485,6 +537,8 @@ if ($database === LIVE) {
name TEXT NOT NULL, name TEXT NOT NULL,
permissions TEXT NOT NULL permissions TEXT NOT NULL
)'); )');
// Create an index for guild_id
$db->exec('CREATE INDEX idx_guild_ranks_guild_id ON guild_ranks (guild_id)');
created_or_error($r, 'guild_ranks'); created_or_error($r, 'guild_ranks');
@ -498,6 +552,10 @@ if ($database === LIVE) {
donated INTEGER NOT NULL DEFAULT 0, donated INTEGER NOT NULL DEFAULT 0,
joined DATETIME DEFAULT CURRENT_TIMESTAMP joined DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// Create an index for guild_id
$db->exec('CREATE INDEX idx_guild_members_guild_id ON guild_members (guild_id)');
// Create an index for char_id
$db->exec('CREATE INDEX idx_guild_members_char_id ON guild_members (char_id)');
created_or_error($r, 'guild_members'); created_or_error($r, 'guild_members');
@ -514,6 +572,8 @@ if ($database === LIVE) {
created DATETIME DEFAULT CURRENT_TIMESTAMP, created DATETIME DEFAULT CURRENT_TIMESTAMP,
updated DATETIME DEFAULT CURRENT_TIMESTAMP updated DATETIME DEFAULT CURRENT_TIMESTAMP
)'); )');
// Create an index for the x, y location
$db->exec('CREATE INDEX idx_npcs_location ON npcs (x, y)');
created_or_error($r, 'npcs'); created_or_error($r, 'npcs');
@ -524,6 +584,8 @@ if ($database === LIVE) {
town_id INTEGER NOT NULL, town_id INTEGER NOT NULL,
rep INTEGER NOT NULL DEFAULT 0 rep INTEGER NOT NULL DEFAULT 0
)'); )');
// Create an index for char_id
$db->exec('CREATE INDEX idx_char_town_rep_char_id ON char_town_rep (char_id)');
created_or_error($r, 'char_town_rep'); created_or_error($r, 'char_town_rep');
@ -568,6 +630,8 @@ if ($database === LIVE) {
trait_id INTEGER NOT NULL, trait_id INTEGER NOT NULL,
value INTEGER NOT NULL DEFAULT 0 value INTEGER NOT NULL DEFAULT 0
)'); )');
// Create an index for char_id
$db->exec('CREATE INDEX idx_char_traits_char_id ON char_traits (char_id)');
created_or_error($r, 'char_traits'); created_or_error($r, 'char_traits');
@ -579,12 +643,16 @@ if ($database === LIVE) {
y INTEGER NOT NULL, y INTEGER NOT NULL,
currently INTEGER NOT NULL DEFAULT 0 currently INTEGER NOT NULL DEFAULT 0
)'); )');
// Create an index for char_id
$db->exec('CREATE INDEX idx_char_locations_char_id ON char_locations (char_id)');
// Create an index for x, y location
$db->exec('CREATE INDEX idx_char_locations_location ON char_locations (x, y)');
created_or_error($r, 'char_locations'); created_or_error($r, 'char_locations');
eln(green('Created database: ') . 'live.db'); eln(green('Created database: ') . 'live.db');
exit(0); if ($database !== 'reset') exit(0);
} }
function created_or_error(bool $result, string $table): void function created_or_error(bool $result, string $table): void

View File

@ -258,3 +258,10 @@ span.badge {
top: 0; top: 0;
left: 0; left: 0;
} }
#debug-query-log {
padding: 2rem;
font-size: 14px;
color: #666;
font-family: monospace;
}

View File

@ -63,8 +63,7 @@ function must_have_character(): void
{ {
// If there is a character selected, make sure the session is up to date. // If there is a character selected, make sure the session is up to date.
if ($_SESSION['user']['char_id'] !== 0) { if ($_SESSION['user']['char_id'] !== 0) {
$char = db_query(db_live(), 'SELECT * FROM characters WHERE id = :c', [':c' => $_SESSION['user']['char_id']])->fetchArray(SQLITE3_ASSOC); char();
$_SESSION['char'] = $char;
return; return;
} }

View File

@ -19,7 +19,7 @@ function c_logout_button(): string
*/ */
function c_char_bar(): string function c_char_bar(): string
{ {
if (!char()) return ''; if (char() === false) return '';
return render('components/char_bar', ['char' => char()]); return render('components/char_bar', ['char' => char()]);
} }
@ -30,3 +30,11 @@ function c_left_nav(int $activeTab): string
{ {
return render('components/left_nav', ['activeTab' => $activeTab]); return render('components/left_nav', ['activeTab' => $activeTab]);
} }
/**
* Render the debug query log.
*/
function c_debug_query_log(): string
{
return render('components/debug_query_log');
}

View File

@ -84,6 +84,7 @@ function auth_controller_register_post(): void
if ($user === false) router_error(400); if ($user === false) router_error(400);
$_SESSION['user'] = user_find($u); $_SESSION['user'] = user_find($u);
wallet_create($_SESSION['user']['id']);
redirect('/character/create-first'); redirect('/character/create-first');
} }

View File

@ -67,7 +67,6 @@ function char_controller_create_post(): void
// Create the auxiliary tables // Create the auxiliary tables
char_location_create($char); char_location_create($char);
char_wallet_create($char);
char_gear_create($char); char_gear_create($char);
// Set the character as the user's selected character // Set the character as the user's selected character

View File

@ -1,11 +1,28 @@
<?php <?php
/**
* Open a connection to a database.
*/
function db_open(string $path): SQLite3
{
$db = new SQLite3($path);
// Increase cache size to 32MB
$db->exec('PRAGMA cache_size = 32000');
// Enable WAL mode
$db->exec('PRAGMA journal_mode = WAL');
// Move temp store to memory
$db->exec('PRAGMA temp_store = MEMORY');
return $db;
}
/** /**
* Return a connection to the auth database. * Return a connection to the auth database.
*/ */
function db_auth(): SQLite3 function db_auth(): SQLite3
{ {
return $GLOBALS['db_auth'] ??= new SQLite3(__DIR__ . '/../database/auth.db'); return $GLOBALS['db_auth'] ??= db_open(__DIR__ . '/../database/auth.db');
} }
/** /**
@ -13,7 +30,7 @@ function db_auth(): SQLite3
*/ */
function db_live(): SQLite3 function db_live(): SQLite3
{ {
return $GLOBALS['db_live'] ??= new SQLite3(__DIR__ . '/../database/live.db'); return $GLOBALS['db_live'] ??= db_open(__DIR__ . '/../database/live.db');
} }
@ -22,7 +39,7 @@ function db_live(): SQLite3
*/ */
function db_fights(): SQLite3 function db_fights(): SQLite3
{ {
return $GLOBALS['db_fights'] ??= new SQLite3(__DIR__ . '/../database/fights.db'); return $GLOBALS['db_fights'] ??= db_open(__DIR__ . '/../database/fights.db');
} }
@ -31,7 +48,7 @@ function db_fights(): SQLite3
*/ */
function db_blueprints(): SQLite3 function db_blueprints(): SQLite3
{ {
return $GLOBALS['db_blueprints'] ??= new SQLite3(__DIR__ . '/../database/blueprints.db'); return $GLOBALS['db_blueprints'] ??= db_open(__DIR__ . '/../database/blueprints.db');
} }
/** /**
@ -43,6 +60,7 @@ function db_query(SQLite3 $db, string $query, array $params = []): SQLite3Result
$stmt = $db->prepare($query); $stmt = $db->prepare($query);
if (!empty($params)) foreach ($params as $key => $value) $stmt->bindValue($key, $value, getSQLiteType($value)); if (!empty($params)) foreach ($params as $key => $value) $stmt->bindValue($key, $value, getSQLiteType($value));
$GLOBALS['queries']++; $GLOBALS['queries']++;
db_log($query);
return $stmt->execute(); return $stmt->execute();
} }
@ -52,6 +70,7 @@ function db_query(SQLite3 $db, string $query, array $params = []): SQLite3Result
function db_exec(SQLite3 $db, string $query): bool function db_exec(SQLite3 $db, string $query): bool
{ {
$GLOBALS['queries']++; $GLOBALS['queries']++;
db_log($query);
return $db->exec($query); return $db->exec($query);
} }
@ -61,7 +80,9 @@ function db_exec(SQLite3 $db, string $query): bool
*/ */
function db_exists(SQLite3 $db, string $table, string $column, mixed $value): bool function db_exists(SQLite3 $db, string $table, string $column, mixed $value): bool
{ {
$result = db_query($db, "SELECT 1 FROM $table WHERE $column = :v LIMIT 1", [':v' => $value]); $query = "SELECT 1 FROM $table WHERE $column = :v LIMIT 1";
$result = db_query($db, $query, [':v' => $value]);
db_log($query);
return $result->fetchArray(SQLITE3_NUM) !== false; return $result->fetchArray(SQLITE3_NUM) !== false;
} }
@ -77,3 +98,11 @@ function getSQLiteType(mixed $value): int
default => SQLITE3_TEXT default => SQLITE3_TEXT
}; };
} }
/**
* Log the given query string to the db debug log.
*/
function db_log(string $query): void
{
if (env('debug', false)) $GLOBALS['query_log'][] = $query;
}

View File

@ -129,13 +129,23 @@ function user_selected_char(): int
/** /**
* If the current user has a selected char and the data is in the session, retrieve either the full array of data * If the current user has a selected char and the data is in the session, retrieve either the full array of data
* or a specific field. * or a specific field. If there is no character data, populate it.
*/ */
function char(string $field = ''): mixed function char(string $field = ''): mixed
{ {
if (empty($_SESSION['char'])) return false; // If there is no user, return false
if ($field === '') return $_SESSION['char']; if (empty($_SESSION['user'])) return false;
return $_SESSION['char'][$field] ?? false;
if (empty($GLOBALS['char'])) {
$GLOBALS['char'] = db_query(
db_live(),
"SELECT * FROM characters WHERE id = :c",
[':c' => user_selected_char()]
)->fetchArray(SQLITE3_ASSOC);
}
if ($field === '') return $GLOBALS['char'];
return $GLOBALS['char'][$field] ?? false;
} }
/** /**
@ -145,7 +155,7 @@ function change_user_character(int $char_id): void
{ {
$_SESSION['user']['char_id'] = $char_id; $_SESSION['user']['char_id'] = $char_id;
db_query(db_auth(), "UPDATE users SET char_id = :c WHERE id = :u", [':c' => $char_id, ':u' => user('id')]); db_query(db_auth(), "UPDATE users SET char_id = :c WHERE id = :u", [':c' => $char_id, ':u' => user('id')]);
$_SESSION['char'] = char_find($char_id); $GLOBALS['char'] = char_find($char_id);
} }
/** /**
@ -157,3 +167,22 @@ function percent(int $num, int $denom, int $precision = 4): int
$p = ($num / $denom) * 100; $p = ($num / $denom) * 100;
return $p < 0 ? 0 : round($p, $precision); return $p < 0 ? 0 : round($p, $precision);
} }
/**
* Access the account wallet. On first execution it will populate $GLOBALS['wallet'] with the wallet data. This way
* the data is up to date with every request without having to query the database every use within, for example, a
* template. Will return false if the field does not exist, or the entire wallet array if no field is specified.
*/
function wallet(string $field = ''): array|int|false
{
if (empty($GLOBALS['wallet'])) {
$GLOBALS['wallet'] = db_query(
db_live(),
"SELECT * FROM wallets WHERE user_id = :u",
[':u' => user('id')]
)->fetchArray(SQLITE3_ASSOC);
}
if ($field === '') return $GLOBALS['wallet'];
return $GLOBALS['wallet'][$field] ?? false;
}

View File

@ -61,22 +61,6 @@ function char_location_create(int $char_id, int $x = 0, int $y = 0, int $current
} }
} }
/**
* Creates a character's wallet. A character's wallet is where they store their currencies. Can optionally specify the
* starting balances of the wallet. Returns the created wallet's ID. If a currency is set to -1, the starting_silver
* or starting_star_gems fields from the env will be used.
*/
function char_wallet_create(int $char_id, int $silver = -1, int $starGems = -1): void
{
if (db_query(db_live(), "INSERT INTO char_wallets (char_id, silver, stargem) VALUES (:p, :s, :sg)", [
':p' => $char_id,
':s' => $silver === -1 ? env('start_silver', 10) : $silver,
':sg' => $starGems === -1 ? env('start_star_gems', 0) : $starGems
]) === false) {
throw new Exception('Failed to create character wallet. (cwc)');
}
}
/** /**
* Create the character's gear table. A character's gear is where they store their equipped items. * Create the character's gear table. A character's gear is where they store their equipped items.
* @TODO: implement initial gear * @TODO: implement initial gear
@ -152,16 +136,6 @@ function char_get_location(int $char_id): array
return $location; return $location;
} }
/**
* Get a character's wallet by their character ID. Returns the wallet's data as an associative array.
*/
function char_get_wallet(int $char_id): array
{
$wallet = db_query(db_live(), "SELECT * FROM char_wallets WHERE char_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC);
if ($wallet === false) throw new Exception('Wallet not found. (cgw)');
return $wallet;
}
/** /**
* See if a character name exists. * See if a character name exists.
*/ */

View File

@ -35,3 +35,19 @@ function user_delete(string|int $user): SQLite3Result|false
{ {
return db_query(db_auth(), "DELETE FROM users WHERE username = :u OR email = :u OR id = :u", [':u' => $user]); return db_query(db_auth(), "DELETE FROM users WHERE username = :u OR email = :u OR id = :u", [':u' => $user]);
} }
/**
* Creates a character's wallet. A character's wallet is where they store their currencies. Can optionally specify the
* starting balances of the wallet. Returns the created wallet's ID. If a currency is set to -1, the starting_silver
* or starting_star_gems fields from the env will be used.
*/
function wallet_create(int $user_id, int $silver = -1, int $starGems = -1): void
{
if (db_query(db_live(), "INSERT INTO wallets (user_id, silver, stargem) VALUES (:u, :s, :sg)", [
':u' => $user_id,
':s' => $silver === -1 ? env('start_silver', 10) : $silver,
':sg' => $starGems === -1 ? env('start_star_gems', 0) : $starGems
]) === false) {
throw new Exception('Failed to create wallet. (wc)');
}
}

View File

@ -27,4 +27,8 @@
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Travel Points<br><?= $char['current_tp'] ?> / <?= $char['max_tp'] ?>"></div> <div class="tooltip-trigger tooltip-hover" data-tooltip-content="Travel Points<br><?= $char['current_tp'] ?> / <?= $char['max_tp'] ?>"></div>
</div> </div>
</div> </div>
<div>
<?= wallet('silver') ?> Silver
</div>
</div> </div>

View File

@ -0,0 +1,5 @@
<div id="debug-query-log">
<h3>Query Log</h3>
<p class="mb-2"><?= $GLOBALS['queries'] ?> queries were executed.</p>
<?php if (!empty($GLOBALS['query_log'])) foreach ($GLOBALS['query_log'] as $query) echo "<p>$query</p>"; ?>
</div>

View File

@ -49,6 +49,8 @@
<p>v<?= env('version') ?></p> <p>v<?= env('version') ?></p>
</footer> </footer>
<?php if (env('debug', false)) echo c_debug_query_log(); ?>
<script type="module"> <script type="module">
import Tooltip from '/assets/scripts/tooltip.js'; import Tooltip from '/assets/scripts/tooltip.js';
Tooltip.init(); Tooltip.init();

View File

@ -1,10 +1,9 @@
<h1>Characters</h1> <h1>Characters</h1>
<?php <?php
$list = char_list(user('id')); if (count($chars) > 0): ?>
if (count($list) > 0): ?>
<form action="/character/select" method="post"> <form action="/character/select" method="post">
<input type="hidden" name="csrf" value="<?= csrf() ?>"> <input type="hidden" name="csrf" value="<?= csrf() ?>">
<?php foreach ($list as $id => $char): ?> <?php foreach ($chars as $id => $char): ?>
<input type="radio" name="char_id" value="<?= $id ?>" id="char_<?= $id ?>"> <input type="radio" name="char_id" value="<?= $id ?>" id="char_<?= $id ?>">
<label for="char_<?= $id ?>"><?= $char['name'] ?> (Level <?= $char['level'] ?>)</label><br> <label for="char_<?= $id ?>"><?= $char['name'] ?> (Level <?= $char['level'] ?>)</label><br>
<?php endforeach; ?> <?php endforeach; ?>