From 8b03b209dce50bc22777744724f65d83664a8826 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Thu, 5 Dec 2024 18:34:12 -0600 Subject: [PATCH] refactor namespaces, start work on sql --- database/create/live.sql | 147 ++++++++++++++++++++------------------- public/index.php | 84 +++++++--------------- src/actions/auth.php | 81 +++++++++++++++++++-- src/bootstrap.php | 30 ++++---- src/components.php | 2 + src/database.php | 10 +-- src/helpers.php | 15 ++-- src/models/character.php | 12 ++-- src/models/session.php | 4 +- src/models/user.php | 34 +++------ src/models/wallet.php | 10 +-- 11 files changed, 228 insertions(+), 201 deletions(-) diff --git a/database/create/live.sql b/database/create/live.sql index b2cd30e..7827688 100644 --- a/database/create/live.sql +++ b/database/create/live.sql @@ -1,93 +1,96 @@ /* - @BLOG + ============================================================ + Stats + ============================================================ */ -DROP TABLE IF EXISTS blog; -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 INDEX idx_blog_author_id ON blog (`author_id`); -CREATE INDEX idx_blog_slug ON blog (`slug`); +CREATE TABLE stats ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `luck` INTEGER NOT NULL DEFAULT 0, + `armor` INTEGER NOT NULL DEFAULT 0, + `focus` INTEGER NOT NULL DEFAULT 0, + `power` INTEGER NOT NULL DEFAULT 0, + `resist` INTEGER NOT NULL DEFAULT 0, + `accuracy` INTEGER NOT NULL DEFAULT 0, + `ferocity` INTEGER NOT NULL DEFAULT 0, + `precision` INTEGER NOT NULL DEFAULT 0, + `toughness` INTEGER NOT NULL DEFAULT 0, + `penetration` INTEGER NOT NULL DEFAULT 0, +) STRICT; /* - @CHARS + ============================================================ + Characters + ============================================================ */ -DROP TABLE IF EXISTS characters; CREATE TABLE characters ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT, - `user_id` INTEGER NOT NULL, - `name` TEXT NOT NULL UNIQUE, - `title_id` INTEGER NOT NULL DEFAULT 1, - `level` INTEGER NOT NULL DEFAULT 1, - `xp` INTEGER NOT NULL DEFAULT 0, + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `user_id` INTEGER NOT NULL, + `name` TEXT NOT NULL UNIQUE, + `title_id` INTEGER NOT NULL DEFAULT 1, + `level` INTEGER NOT NULL DEFAULT 1, + `xp` INTEGER NOT NULL DEFAULT 0, `xp_to_level` INTEGER NOT NULL DEFAULT 100, - `hp` INTEGER NOT NULL DEFAULT 20, - `m_hp` INTEGER NOT NULL DEFAULT 20, - `mp` INTEGER NOT NULL DEFAULT 10, - `m_mp` INTEGER NOT NULL DEFAULT 10, - `tp` INTEGER NOT NULL DEFAULT 1, - `m_tp` INTEGER NOT NULL DEFAULT 1, - `pow` INTEGER NOT NULL DEFAULT 0, -- Power - `acc` INTEGER NOT NULL DEFAULT 0, -- Accuracy - `pen` INTEGER NOT NULL DEFAULT 0, -- Penetration - `foc` INTEGER NOT NULL DEFAULT 0, -- Focus - `tou` INTEGER NOT NULL DEFAULT 0, -- Toughness - `arm` INTEGER NOT NULL DEFAULT 0, -- Armor - `res` INTEGER NOT NULL DEFAULT 0, -- Resist - `pre` INTEGER NOT NULL DEFAULT 0, -- Precision - `fer` INTEGER NOT NULL DEFAULT 0, -- Ferocity - `luck` INTEGER NOT NULL DEFAULT 0, -- Luck - `inv_slots` INTEGER NOT NULL DEFAULT 10, - `att_points` INTEGER NOT NULL DEFAULT 0, - `bio` TEXT DEFAULT '' + `hp` INTEGER NOT NULL DEFAULT 20, + `m_hp` INTEGER NOT NULL DEFAULT 20, + `mp` INTEGER NOT NULL DEFAULT 10, + `m_mp` INTEGER NOT NULL DEFAULT 10, + `tp` INTEGER NOT NULL DEFAULT 1, + `m_tp` INTEGER NOT NULL DEFAULT 1, + `stats_id` INTEGER NOT NULL, + `inv_slots` INTEGER NOT NULL DEFAULT 10, + `att_points` INTEGER NOT NULL DEFAULT 0, + `bio` TEXT DEFAULT '' ); CREATE INDEX idx_characters_user_id ON characters (`user_id`); -/* - @CHARGEAR -*/ -DROP TABLE IF EXISTS char_gear; -CREATE TABLE char_gear ( - `char_id` INTEGER NOT NULL, - `head` INTEGER NOT NULL DEFAULT 0, - `chest` INTEGER NOT NULL DEFAULT 0, - `boots` INTEGER NOT NULL DEFAULT 0, - `hands` INTEGER NOT NULL DEFAULT 0, - `m_hand` INTEGER NOT NULL DEFAULT 0, - `o_hand` INTEGER NOT NULL DEFAULT 0, - `rune` INTEGER NOT NULL DEFAULT 0, - `ring` INTEGER NOT NULL DEFAULT 0, - `amulet` INTEGER NOT NULL DEFAULT 0, - `pow` INTEGER NOT NULL DEFAULT 0, -- Power - `acc` INTEGER NOT NULL DEFAULT 0, -- Accuracy - `pen` INTEGER NOT NULL DEFAULT 0, -- Penetration - `foc` INTEGER NOT NULL DEFAULT 0, -- Focus - `tou` INTEGER NOT NULL DEFAULT 0, -- Toughness - `arm` INTEGER NOT NULL DEFAULT 0, -- Armor - `res` INTEGER NOT NULL DEFAULT 0, -- Resist - `pre` INTEGER NOT NULL DEFAULT 0, -- Precision - `fer` INTEGER NOT NULL DEFAULT 0, -- Ferocity - `luck` INTEGER NOT NULL DEFAULT 0, -- Luck - `max_hp` INTEGER NOT NULL DEFAULT 0, - `max_mp` INTEGER NOT NULL DEFAULT 0 +CREATE TABLE equipped_items ( + `char_id` INTEGER NOT NULL, + `head` INTEGER NOT NULL DEFAULT 0, + `chest` INTEGER NOT NULL DEFAULT 0, + `boots` INTEGER NOT NULL DEFAULT 0, + `hands` INTEGER NOT NULL DEFAULT 0, + `m_hand` INTEGER NOT NULL DEFAULT 0, + `o_hand` INTEGER NOT NULL DEFAULT 0, + `rune` INTEGER NOT NULL DEFAULT 0, + `ring` INTEGER NOT NULL DEFAULT 0, + `amulet` INTEGER NOT NULL DEFAULT 0, + `stats_id` INTEGER NOT NULL, + `max_hp` INTEGER NOT NULL DEFAULT 0, + `max_mp` INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX idx_char_gear_char_id ON char_gear (`char_id`); -/* - @CHARINV -*/ -DROP TABLE IF EXISTS char_inventory; -CREATE TABLE char_inventory ( +CREATE TABLE inventory_items ( `char_id` INTEGER NOT NULL, `item_id` INTEGER NOT NULL ); CREATE INDEX idx_inventory_char_id ON char_inventory (`char_id`); +-- Wallets are account-bound rather than character-bound. Should I move this to auth? +CREATE TABLE wallets ( + `user_id` INTEGER NOT NULL, + `silver` INTEGER NOT NULL DEFAULT 10, + `stargem` INTEGER NOT NULL DEFAULT 0 +); +CREATE INDEX idx_wallets_user_id ON wallets (`user_id`); + +/* + ============================================================ + Blog + ============================================================ +*/ +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 INDEX idx_blog_author_id ON blog (`author_id`); +CREATE INDEX idx_blog_slug ON blog (`slug`); + /* @WALLETS */ diff --git a/public/index.php b/public/index.php index ac85b78..e4bf76b 100644 --- a/public/index.php +++ b/public/index.php @@ -1,7 +1,11 @@ get('/', function() { if (user()) redirect('/world'); @@ -18,73 +24,23 @@ $r->get('/', function() { }); /* + ============================================================ Auth + ============================================================ */ $r->get('/register', 'Actions\Auth::register_get')->middleware('guest_only'); $r->post('/register', 'Actions\Auth::register_post')->middleware('guest_only'); -$r->get('/login', function() { - echo render('layouts/basic', ['view' => 'pages/auth/login']); -})->middleware('guest_only'); +$r->get('/login', 'Actions\Auth::login_get')->middleware('guest_only'); +$r->post('/login', 'Actions\Auth::login_post')->middleware('guest_only'); -$r->post('/login', function() { - $errors = []; - - $u = trim($_POST['u'] ?? ''); - $p = $_POST['p'] ?? ''; - - if (empty($u)) $errors['u'][] = 'Username is required.'; - if (empty($p)) $errors['p'][] = 'Password is required.'; - - // If there are errors at this point, send them to the page with errors flashed. - if (!empty($errors)) { - $GLOBALS['form-errors'] = $errors; - echo render('layouts/basic', ['view' => 'pages/auth/login']); - exit; - } - - $user = User::find($u); - if ($user === false || !$user->check_password($p)) { - $errors['x'][] = 'Invalid username or password.'; - $GLOBALS['form-errors'] = $errors; - echo render('layouts/basic', ['view' => 'pages/auth/login']); - exit; - } - - $_SESSION['user'] = serialize($user); - - if ($_POST['remember'] ?? false) { - $session = Session::create($user->id, strtotime('+30 days')); - if ($session === false) error_response(400); - set_cookie('remember_me', $session->token, $session->expires); - } - - if ($user->char_count() === 0) { - redirect('/character/create-first'); - } elseif (!change_user_character($user->char_id)) { - echo "failed to change user character (aclp)"; - error_response(999); - } - - redirect('/'); -})->middleware('guest_only'); - -$r->post('/logout', function() { - Session::delete(user()->id); - unset($_SESSION['user']); - set_cookie('remember_me', '', 1); - redirect('/'); -}); - -$r->get('/debug/logout', function() { - Session::delete(user()->id); - unset($_SESSION['user']); - set_cookie('remember_me', '', 1); - redirect('/'); -}); +$r->post('/logout', 'Actions\Auth::logout')->middleware('auth_only'); +if (env('debug', false)) $r->get('/debug/logout', 'Actions\Auth::logout'); /* + ============================================================ Characters + ============================================================ */ $r->get('/characters', function() { //echo page('chars/list', ['chars' => user()->char_list()]); @@ -220,7 +176,9 @@ $r->post('/character/delete', function() { })->middleware('must_have_character'); /* + ============================================================ World + ============================================================ */ $r->get('/world', function() { echo render('layouts/game'); @@ -270,14 +228,18 @@ $r->post('/move', function() { })->middleware('ajax_only')->middleware('must_have_character'); /* - UI + ============================================================ + UI Components + ============================================================ */ $r->get('/ui/stats', function() { echo c_profile_stats(char()); })->middleware('ajax_only')->middleware('must_have_character'); /* + ============================================================ Router + ============================================================ */ // [code, handler, params, middleware] $l = $r->lookup($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']); @@ -287,6 +249,8 @@ if (!empty($l['middleware'])) foreach ($l['middleware'] as $middleware) $middlew $l['handler'](...$l['params'] ?? []); /* + ============================================================ Cleanup + ============================================================ */ clear_flashes(); diff --git a/src/actions/auth.php b/src/actions/auth.php index af17251..77f141b 100644 --- a/src/actions/auth.php +++ b/src/actions/auth.php @@ -2,10 +2,17 @@ namespace Actions; -use \User; +use Models\Session; +use Models\User; +use Models\Wallet; class Auth { + /* + ============================================================ + Registration + ============================================================ + */ public static function register_get(): void { echo render('layouts/basic', ['view' => 'pages/auth/register']); @@ -67,10 +74,76 @@ class Auth exit; } - if (\User::create($u, $e, $p) === false) error_response(400); + if (User::create($u, $e, $p) === false) error_response(400); - $_SESSION['user'] = serialize(\User::find($u)); - \Wallet::create(user()->id); + $_SESSION['user'] = serialize(User::find($u)); + Wallet::create(user()->id); redirect('/character/create-first'); } + + /* + ============================================================ + Login + ============================================================ + */ + public static function login_get(): void + { + echo render('layouts/basic', ['view' => 'pages/auth/login']); + } + + public static function login_post(): void + { + $errors = []; + + $u = trim($_POST['u'] ?? ''); + $p = $_POST['p'] ?? ''; + + if (empty($u)) $errors['u'][] = 'Username is required.'; + if (empty($p)) $errors['p'][] = 'Password is required.'; + + // If there are errors at this point, send them to the page with errors flashed. + if (!empty($errors)) { + $GLOBALS['form-errors'] = $errors; + echo render('layouts/basic', ['view' => 'pages/auth/login']); + exit; + } + + $user = User::find($u); + if ($user === false || !$user->check_password($p)) { + $errors['x'][] = 'Invalid username or password.'; + $GLOBALS['form-errors'] = $errors; + echo render('layouts/basic', ['view' => 'pages/auth/login']); + exit; + } + + $_SESSION['user'] = serialize($user); + + if ($_POST['remember'] ?? false) { + $session = Session::create($user->id, strtotime('+30 days')); + if ($session === false) error_response(400); + set_cookie('remember_me', $session->token, $session->expires); + } + + if ($user->char_count() === 0) { + redirect('/character/create-first'); + } elseif (!change_user_character($user->char_id)) { + echo "failed to change user character (aclp)"; + error_response(999); + } + + redirect('/'); + } + + /* + ============================================================ + Logout + ============================================================ + */ + public static function logout(): void + { + Session::delete(user()->id); + unset($_SESSION['user']); + set_cookie('remember_me', '', 1); + redirect('/'); + } } diff --git a/src/bootstrap.php b/src/bootstrap.php index 5ff6384..e3bbcd2 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -24,13 +24,13 @@ session_start(); ============================================================ */ define('CLASS_MAP', [ - 'Database' => '/database.php', - 'Router' => '/router.php', - 'User' => '/models/user.php', - 'Character' => '/models/character.php', - 'Wallet' => '/models/wallet.php', - 'Session' => '/models/session.php', - 'Actions\Auth' => '/actions/auth.php', + 'Database' => '/database.php', + 'Router' => '/router.php', + 'Actions\Auth' => '/actions/auth.php', + 'Models\User' => '/models/user.php', + 'Models\Character' => '/models/character.php', + 'Models\Wallet' => '/models/wallet.php', + 'Models\Session' => '/models/session.php', ]); spl_autoload_register(function (string $class) { @@ -58,12 +58,11 @@ if (env('debug', false)) { CSRF ============================================================ */ -csrf(); // generate a CSRF token, or retrieve the current token +csrf(); -// error any request that fails CSRF on these methods if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])) { - $csrf = $_POST['csrf'] ?? $_SERVER['HTTP_X_CSRF'] ?? ''; - if (!hash_equals($_SESSION['csrf'] ?? '', $csrf)) error_response(418); + $csrf = $_POST['csrf'] ?? $_SERVER['HTTP_X_CSRF'] ?? ''; // look for CSRF in AJAX requests + if (!hash_equals($_SESSION['csrf'] ?? '', $csrf)) error_response(418); // I'm a Teapot } /* @@ -71,9 +70,10 @@ if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])) { Global State ============================================================ */ -$GLOBALS['databases'] = []; // database interfaces - -// all relevant state to handling requests +$GLOBALS['databases'] = []; $GLOBALS['state'] = [ - 'logged_in' => isset($_SESSION['user']) || validate_session() + 'logged_in' => isset($_SESSION['user']) || validate_session(), + 'user' => null, // populated by user() + 'char' => null, // user's selected character, populated by char() + 'wallet' => null, // populated by wallet() ]; diff --git a/src/components.php b/src/components.php index 8a97716..90392b4 100644 --- a/src/components.php +++ b/src/components.php @@ -1,5 +1,7 @@ prepare($query); - if (!empty($params)) { - foreach ($params as $k => $v) { - $stmt->bindValue($p ? $k + 1 : $k, $v, $this->getSQLiteType($v)); - } - } + foreach ($params ?? [] as $k => $v) $stmt->bindValue($p ? $k + 1 : $k, $v, $this->getSQLiteType($v)); $start = microtime(true); $r = $stmt->execute(); @@ -61,9 +57,7 @@ class Database extends SQLite3 $this->count++; $this->query_time += $time_taken; - if (env('debug', false)) { - $this->log[] = [$query, $time_taken]; - } + if (env('debug', false)) $this->log[] = [$query, $time_taken]; } private function getSQLiteType(mixed $value): int diff --git a/src/helpers.php b/src/helpers.php index 64dcae8..0e2b1f8 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -1,5 +1,7 @@ current_char(); - return $GLOBALS['char']; + return $GLOBALS['state']['char'] ??= Character::find(user()->char_id); } /** @@ -233,12 +234,10 @@ function percent(float $num, float $denom, int $precision = 4): float * 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 user or wallet does not exist. */ -function wallet(): Wallet|false +function wallet(): Models\Wallet|false { if (user() === false) return false; - if (empty($GLOBALS['wallet'])) $w = user()->wallet(); - if ($w === false) return false; - return $GLOBALS['wallet'] = $w; + return $GLOBALS['state']['wallet'] = Models\Wallet::find(user()->id); } /** @@ -396,7 +395,7 @@ function db_fetch_array(SQLite3Result $result, int $mode = SQLITE3_ASSOC): array function validate_session(): bool { if (!isset($_COOKIE['remember_me'])) return false; - if (($session = Session::find($_COOKIE['remember_me'])) && $session->validate()) return true; + if (($session = Models\Session::find($_COOKIE['remember_me'])) && $session->validate()) return true; return false; } diff --git a/src/models/character.php b/src/models/character.php index 3bfbaf1..50a3c9e 100644 --- a/src/models/character.php +++ b/src/models/character.php @@ -1,5 +1,7 @@ $id] ); - if ($q === false) throw new Exception('Failed to query character. (C::f)'); // badly formed query + if ($q === false) throw new \Exception('Failed to query character. (C::f)'); // badly formed query return ($c = $q->fetchArray(SQLITE3_ASSOC)) === false ? false : new Character($c); } @@ -99,7 +101,7 @@ class Character // Create the character! if (live_db()->query("INSERT INTO characters ($f) VALUES ($v)", $data) === false) { // @TODO: Log this error - throw new Exception('Failed to create character. (cc)'); + throw new \Exception('Failed to create character. (cc)'); } // Get the character ID @@ -153,7 +155,7 @@ class Character "SELECT 1 FROM characters WHERE id = :i AND user_id = :u LIMIT 1", [':i' => $id, ':u' => $user_id] ); - if ($q === false) throw new Exception('Failed to query char ownership. (C::bt)'); + if ($q === false) throw new \Exception('Failed to query char ownership. (C::bt)'); return $q->fetchArray(SQLITE3_ASSOC) !== false; } @@ -186,7 +188,7 @@ class Character { // Delete the character if (live_db()->query("DELETE FROM characters WHERE id = :p", [':p' => $id]) === false) { - throw new Exception('Failed to delete character. (C::d)'); + throw new \Exception('Failed to delete character. (C::d)'); } } @@ -203,7 +205,7 @@ class Character 'SELECT awarded FROM owned_titles WHERE char_id = ? AND title_id = ? LIMIT 1', [$this->id, $this->title_id] ); - if ($q === false) throw new Exception('Failed to query title. (C::t)'); + if ($q === false) throw new \Exception('Failed to query title. (C::t)'); $a = $q->fetchArray(SQLITE3_ASSOC); if ($a === false) return false; diff --git a/src/models/session.php b/src/models/session.php index e7a55b5..5aa108a 100644 --- a/src/models/session.php +++ b/src/models/session.php @@ -1,5 +1,7 @@ query("DELETE FROM sessions WHERE user_id = :u", [':u' => $user_id]); } diff --git a/src/models/user.php b/src/models/user.php index 7e45bc1..e3ae358 100644 --- a/src/models/user.php +++ b/src/models/user.php @@ -1,5 +1,7 @@ $v) { if (property_exists($this, $k)) { - $this->$k = in_array($k, ['created', 'last_login']) ? new DateTime($v) : $v; + $this->$k = in_array($k, ['created', 'last_login']) ? strtotime($v) : $v; } } } @@ -72,7 +74,7 @@ class User "SELECT * FROM users WHERE username = :i COLLATE NOCASE OR email = :i COLLATE NOCASE OR id = :i LIMIT 1", [':i' => $identifier] ); - if ($r === false) throw new Exception("Failed to query user. (U::f)"); // badly formed query + if ($r === false) throw new \Exception("Failed to query user. (U::f)"); // badly formed query $u = $r->fetchArray(SQLITE3_ASSOC); if ($u === false) return false; // no user found return new User($u); @@ -83,7 +85,7 @@ class User * of the username or password passed to it; that is the responsibility of the caller. Returns false on * failure. */ - public static function create(string $username, string $email, string $password, int $auth = 0): SQLite3Result|false + public static function create(string $username, string $email, string $password, int $auth = 0): \SQLite3Result|false { return auth_db()->query("INSERT INTO users (username, email, password, auth) VALUES (:u, :e, :p, :a)", [ ':u' => $username, @@ -120,7 +122,7 @@ class User /** * Delete a user by their username, email, or id. */ - public static function delete(string|int $identifier): SQLite3Result|false + public static function delete(string|int $identifier): \SQLite3Result|false { return auth_db()->query( "DELETE FROM users WHERE username = :i OR email = :i OR id = :i", @@ -137,7 +139,7 @@ class User "SELECT COUNT(*) FROM characters WHERE user_id = :u", [':u' => $this->id] )->fetchArray(SQLITE3_NUM); - if ($c === false) throw new Exception('Failed to count characters. (U::cc)'); + if ($c === false) throw new \Exception('Failed to count characters. (U::cc)'); return (int) $c[0]; } @@ -148,7 +150,7 @@ class User public function char_list(): array|false { $q = live_db()->query("SELECT id, name, level FROM characters WHERE user_id = ?", [$this->id]); - if ($q === false) throw new Exception('Failed to list characters. (U->cl)'); + if ($q === false) throw new \Exception('Failed to list characters. (U->cl)'); $c = []; while ($row = $q->fetchArray(SQLITE3_ASSOC)) { @@ -158,20 +160,4 @@ class User // return false if no characters return empty($c) ? false : $c; } - - /** - * Get the user's current Character. - */ - public function current_char(): Character|false - { - return Character::find($this->char_id); - } - - /** - * Get the user's wallet. - */ - public function wallet(): Wallet|false - { - return Wallet::find($this->id); - } } diff --git a/src/models/wallet.php b/src/models/wallet.php index 5609f5e..4d99343 100644 --- a/src/models/wallet.php +++ b/src/models/wallet.php @@ -1,5 +1,7 @@ query('SELECT * FROM wallets WHERE user_id = ?', [$user_id]); - if ($r === false) throw new Exception('Failed to query wallet. (W::f)'); // badly formed query + if ($r === false) throw new \Exception('Failed to query wallet. (W::f)'); // badly formed query $w = $r->fetchArray(SQLITE3_ASSOC); if ($w === false) return false; // no wallet found return new Wallet($user_id, $w['silver'], $w['stargem']); } - public static function create(int $user_id, int $silver = -1, int $starGems = -1): SQLite3Result|false + public static function create(int $user_id, int $silver = -1, int $starGems = -1): \SQLite3Result|false { return live_db()->query( "INSERT INTO wallets (user_id, silver, stargem) VALUES (:u, :s, :sg)", @@ -32,7 +34,7 @@ class Wallet /** * Add a certain amount of currency to the user's wallet. */ - public function give(Currency $c, int $amt): SQLite3Result|false + public function give(\Currency $c, int $amt): \SQLite3Result|false { $cs = $c->string(true); $new = $this->{$cs} + $amt; @@ -42,7 +44,7 @@ class Wallet /** * Remove a certain amount of currency from the user's wallet. */ - public function take(Currency $c, int $amt): SQLite3Result|false + public function take(\Currency $c, int $amt): \SQLite3Result|false { $cs = $c->string(true); $new = $this->{$cs} - $amt;