Class refactor, move char bar
This commit is contained in:
parent
cee92173a4
commit
d3eeaf6ced
File diff suppressed because one or more lines are too long
|
@ -10,6 +10,7 @@ body {
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
max-width: 1640px;
|
max-width: 1640px;
|
||||||
|
min-width: 968px;
|
||||||
margin: 0px auto;
|
margin: 0px auto;
|
||||||
font-family: var(--main-font);
|
font-family: var(--main-font);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +42,6 @@ header#main-header {
|
||||||
main {
|
main {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 968px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
|
|
||||||
|
@ -208,30 +208,14 @@ footer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#char-bar {
|
#character {
|
||||||
display: flex;
|
& > .name {
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
padding: 0 1rem;
|
|
||||||
height: 34px;
|
|
||||||
color: white;
|
|
||||||
gap: 1rem;
|
|
||||||
background-image: url('/assets/img/bar.jpg');
|
|
||||||
|
|
||||||
& > div.container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
}
|
||||||
|
|
||||||
& > div {
|
& > div:not(:last-child) {
|
||||||
display: flex;
|
margin-bottom: 0.5rem;
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
width: 18px;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,10 +295,14 @@ span.badge {
|
||||||
}
|
}
|
||||||
|
|
||||||
.debug-query-log {
|
.debug-query-log {
|
||||||
padding: 2rem;
|
padding: 1rem;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#center > section {
|
#center > section {
|
||||||
|
|
|
@ -57,6 +57,23 @@ router_get($r, '/settings', 'settings_controller_get');
|
||||||
*/
|
*/
|
||||||
router_get($r, '/auctions', 'auctions_controller_get');
|
router_get($r, '/auctions', 'auctions_controller_get');
|
||||||
|
|
||||||
|
/*
|
||||||
|
Testing
|
||||||
|
*/
|
||||||
|
if (env('debug')) {
|
||||||
|
router_get($r, '/give_silver/:x', function (int $amt) {
|
||||||
|
auth_only_and_must_have_character();
|
||||||
|
wallet()->give(Currency::Silver, $amt);
|
||||||
|
redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
router_get($r, '/take_silver/:x', function (int $amt) {
|
||||||
|
auth_only_and_must_have_character();
|
||||||
|
wallet()->take(Currency::Silver, $amt);
|
||||||
|
redirect('/');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Router
|
Router
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4,6 +4,12 @@ session_start();
|
||||||
|
|
||||||
// SRC is defined as the path to the src/ directory from public/
|
// SRC is defined as the path to the src/ directory from public/
|
||||||
|
|
||||||
|
define('CLASS_MAP', [
|
||||||
|
'User' => '/models/user.php',
|
||||||
|
'Character' => '/models/character.php',
|
||||||
|
'Wallet' => '/models/wallet.php'
|
||||||
|
]);
|
||||||
|
|
||||||
// Source libraries
|
// Source libraries
|
||||||
require_once SRC . '/helpers.php';
|
require_once SRC . '/helpers.php';
|
||||||
require_once SRC . '/util/env.php';
|
require_once SRC . '/util/env.php';
|
||||||
|
@ -12,12 +18,11 @@ require_once SRC . '/util/auth.php';
|
||||||
require_once SRC . '/util/router.php';
|
require_once SRC . '/util/router.php';
|
||||||
require_once SRC . '/util/components.php';
|
require_once SRC . '/util/components.php';
|
||||||
require_once SRC . '/util/render.php';
|
require_once SRC . '/util/render.php';
|
||||||
|
require_once SRC . '/util/enums.php';
|
||||||
|
|
||||||
// Database models
|
// Database models
|
||||||
require_once SRC . '/model/user.php';
|
require_once SRC . '/models/session.php';
|
||||||
require_once SRC . '/model/session.php';
|
require_once SRC . '/models/token.php';
|
||||||
require_once SRC . '/model/token.php';
|
|
||||||
require_once SRC . '/model/char.php';
|
|
||||||
|
|
||||||
// Controllers
|
// Controllers
|
||||||
require_once SRC . '/controller/char.php';
|
require_once SRC . '/controller/char.php';
|
||||||
|
@ -27,6 +32,10 @@ require_once SRC . '/controller/settings.php';
|
||||||
require_once SRC . '/controller/auctions.php';
|
require_once SRC . '/controller/auctions.php';
|
||||||
require_once SRC . '/controller/profile.php';
|
require_once SRC . '/controller/profile.php';
|
||||||
|
|
||||||
|
spl_autoload_register(function (string $class) {
|
||||||
|
if (array_key_exists($class, CLASS_MAP)) require_once SRC . CLASS_MAP[$class];
|
||||||
|
});
|
||||||
|
|
||||||
// Track the start time of the request
|
// Track the start time of the request
|
||||||
define('START_TIME', microtime(true));
|
define('START_TIME', microtime(true));
|
||||||
|
|
||||||
|
|
|
@ -53,14 +53,14 @@ function auth_controller_register_post()
|
||||||
/*
|
/*
|
||||||
A username must be unique.
|
A username must be unique.
|
||||||
*/
|
*/
|
||||||
if (auth_username_exists($u)) {
|
if (User::username_exists($u)) {
|
||||||
$errors['u'][] = 'Username is already taken.';
|
$errors['u'][] = 'Username is already taken.';
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
An email must be unique.
|
An email must be unique.
|
||||||
*/
|
*/
|
||||||
if (auth_email_exists($e)) {
|
if (User::email_exists($e)) {
|
||||||
$errors['e'][] = 'Email is already taken.';
|
$errors['e'][] = 'Email is already taken.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +71,10 @@ function auth_controller_register_post()
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = user_create($u, $e, $p);
|
if (User::create($u, $e, $p) === false) router_error(400);
|
||||||
if ($user === false) router_error(400);
|
|
||||||
|
|
||||||
$_SESSION['user'] = user_find($u);
|
$_SESSION['user'] = serialize(User::find($u));
|
||||||
wallet_create($_SESSION['user']['id']);
|
Wallet::create(user()->id);
|
||||||
redirect('/character/create-first');
|
redirect('/character/create-first');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,31 +110,32 @@ function auth_controller_login_post()
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = user_find($u);
|
$user = User::find($u);
|
||||||
if ($user === false || !password_verify($p, $user['password'])) {
|
if ($user === false || !$user->check_password($p)) {
|
||||||
$errors['x'][] = 'Invalid username or password.';
|
$errors['x'][] = 'Invalid username or password.';
|
||||||
$GLOBALS['form-errors'] = $errors;
|
$GLOBALS['form-errors'] = $errors;
|
||||||
echo render('layouts/basic', ['view' => 'pages/auth/login']);
|
echo render('layouts/basic', ['view' => 'pages/auth/login']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION['user'] = $user;
|
$_SESSION['user'] = serialize($user);
|
||||||
|
|
||||||
if ($_POST['remember'] ?? false) {
|
if ($_POST['remember'] ?? false) {
|
||||||
$token = token();
|
$token = token();
|
||||||
$expires = strtotime('+30 days');
|
$expires = strtotime('+30 days');
|
||||||
$result = db_query(db_auth(), "INSERT INTO sessions (token, user_id, expires) VALUES (:t, :u, :e)", [
|
$result = db_query(
|
||||||
':t' => $token,
|
db_auth(),
|
||||||
':u' => $_SESSION['user']['id'],
|
"INSERT INTO sessions (token, user_id, expires) VALUES (:t, :u, :e)",
|
||||||
':e' => $expires
|
[':t' => $token, ':u' => user()->id, ':e' => $expires]
|
||||||
]);
|
);
|
||||||
if (!$result) router_error(400);
|
if (!$result) router_error(400);
|
||||||
set_cookie('remember_me', $token, $expires);
|
set_cookie('remember_me', $token, $expires);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (char_count($_SESSION['user']['id']) === 0) {
|
if (user()->char_count() === 0) {
|
||||||
redirect('/character/create-first');
|
redirect('/character/create-first');
|
||||||
} elseif (!change_user_character($_SESSION['user']['char_id'])) {
|
} elseif (!change_user_character(user()->char_id)) {
|
||||||
|
echo "failed to change user character (aclp)";
|
||||||
router_error(999);
|
router_error(999);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,9 +148,8 @@ function auth_controller_login_post()
|
||||||
function auth_controller_logout_post()
|
function auth_controller_logout_post()
|
||||||
{
|
{
|
||||||
csrf_ensure();
|
csrf_ensure();
|
||||||
session_delete($_SESSION['user']['id']);
|
session_delete(user()->id);
|
||||||
unset($_SESSION['user']);
|
unset($_SESSION['user']);
|
||||||
unset($_SESSION['char']);
|
|
||||||
set_cookie('remember_me', '', 1);
|
set_cookie('remember_me', '', 1);
|
||||||
redirect('/');
|
redirect('/');
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ function char_controller_list_get()
|
||||||
auth_only_and_must_have_character();
|
auth_only_and_must_have_character();
|
||||||
|
|
||||||
$GLOBALS['active_nav_tab'] = 'chars';
|
$GLOBALS['active_nav_tab'] = 'chars';
|
||||||
echo page('chars/list', ['chars' => char_list(user('id'))]);
|
echo page('chars/list', ['chars' => user()->char_list()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,25 +38,23 @@ function char_controller_list_post()
|
||||||
// If the action is to select a character, change the user's selected character.
|
// If the action is to select a character, change the user's selected character.
|
||||||
if ($action === 'select') {
|
if ($action === 'select') {
|
||||||
// If the character ID is the current character, do nothing.
|
// If the character ID is the current character, do nothing.
|
||||||
if ($char_id === $_SESSION['user']['char_id'] || $char_id === 0) {
|
if ($char_id === user()->char_id || $char_id === 0) {
|
||||||
flash('alert_character_list_1', ['info', 'You are already using <b>' . char('name') . '</b>.']);
|
flash('alert_character_list_1', ['info', 'You are already using <b>' . char()->name . '</b>.']);
|
||||||
redirect('/characters');
|
redirect('/characters');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the character ID is valid and belongs to the user.
|
if (!Character::belongs_to($char_id, user()->id)) router_error(999);
|
||||||
if (!char_belongs_to_user($char_id, $_SESSION['user']['id'])) router_error(999);
|
|
||||||
|
|
||||||
change_user_character($char_id);
|
change_user_character($char_id);
|
||||||
|
|
||||||
flash('alert_character_list_1', ['success', 'Switched to <b>' . char('name') . '</b>!']);
|
flash('alert_character_list_1', ['success', 'Switched to <b>' . char()->name . '</b>!']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the action is to delete a character, move to the confirmation page.
|
// If the action is to delete a character, move to the confirmation page.
|
||||||
if ($action === 'delete') {
|
if ($action === 'delete') {
|
||||||
// Ensure the character ID is valid and belongs to the user.
|
if (!Character::belongs_to($char_id, user()->id)) router_error(999);
|
||||||
if (!char_belongs_to_user($char_id, $_SESSION['user']['id'])) router_error(999);
|
|
||||||
|
|
||||||
echo page('chars/delete', ['char' => char_find($char_id)]);
|
echo page('chars/delete', ['char' => Character::find($char_id)]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,9 +74,9 @@ function char_controller_delete_post()
|
||||||
if (!is_numeric($char_id)) router_error(400);
|
if (!is_numeric($char_id)) router_error(400);
|
||||||
|
|
||||||
// Ensure the character ID is valid and belongs to the user.
|
// Ensure the character ID is valid and belongs to the user.
|
||||||
if (!char_belongs_to_user($char_id, $_SESSION['user']['id'])) router_error(999);
|
if (!Character::belongs_to($char_id, user()->id)) router_error(999);
|
||||||
|
|
||||||
$char = char_find($char_id);
|
$char = Character::find($char_id);
|
||||||
|
|
||||||
// Confirm the name matches the name of the character. CASE SENSITIVE.
|
// Confirm the name matches the name of the character. CASE SENSITIVE.
|
||||||
if ($char['name'] !== trim($_POST['n'] ?? '')) {
|
if ($char['name'] !== trim($_POST['n'] ?? '')) {
|
||||||
|
@ -87,11 +85,11 @@ function char_controller_delete_post()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the character
|
// Delete the character
|
||||||
char_delete($char_id);
|
Character::delete($char_id);
|
||||||
|
|
||||||
// If the character being deleted is the currently selected character, select the first character.
|
// If the character being deleted is the currently selected character, select the first character.
|
||||||
if ($_SESSION['user']['char_id'] === $char_id) {
|
if (user()->char_id === $char_id) {
|
||||||
$chars = char_list(user('id'));
|
$chars = user()->char_list();
|
||||||
if (count($chars) > 0) change_user_character($chars[0]['id']);
|
if (count($chars) > 0) change_user_character($chars[0]['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +107,7 @@ function char_controller_create_first_get()
|
||||||
$GLOBALS['active_nav_tab'] = 'chars';
|
$GLOBALS['active_nav_tab'] = 'chars';
|
||||||
|
|
||||||
// If the user already has a character, redirect them to the main page.
|
// If the user already has a character, redirect them to the main page.
|
||||||
if (char_count(user('id')) > 0) redirect('/');
|
if (user()->char_count() > 0) redirect('/');
|
||||||
|
|
||||||
echo page('chars/first');
|
echo page('chars/first');
|
||||||
}
|
}
|
||||||
|
@ -139,7 +137,7 @@ function char_controller_create_post()
|
||||||
/*
|
/*
|
||||||
A character's name must be unique.
|
A character's name must be unique.
|
||||||
*/
|
*/
|
||||||
if (char_name_exists($name)) $errors['n'][] = 'Name is already taken.';
|
if (Character::name_exists($name)) $errors['n'][] = 'Name is already taken.';
|
||||||
|
|
||||||
// If there are errors at this point, send them to the page with errors flashed.
|
// If there are errors at this point, send them to the page with errors flashed.
|
||||||
if (!empty($errors)) {
|
if (!empty($errors)) {
|
||||||
|
@ -151,24 +149,22 @@ function char_controller_create_post()
|
||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
// If this is not the first character, return to the character list page.
|
// If this is not the first character, return to the character list page.
|
||||||
echo page('chars/list', ['chars' => char_list(user('id'))]);
|
echo page('chars/list', ['chars' => user()->char_list()]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the character
|
if (($char = Character::create(user()->id, $name)) === false) router_error(400);
|
||||||
$char = char_create(user('id'), $name);
|
|
||||||
if ($char === false) router_error(400);
|
|
||||||
|
|
||||||
// Create the auxiliary tables
|
// Create the auxiliary tables
|
||||||
char_location_create($char);
|
$char->create_location();
|
||||||
char_gear_create($char);
|
$char->create_gear();
|
||||||
|
|
||||||
// Award the Adventurer title.
|
// Award the Adventurer title.
|
||||||
char_award_title(1, $char);
|
$char->award_title(1);
|
||||||
|
|
||||||
// Set the character as the user's selected character
|
// Set the character as the user's selected character
|
||||||
change_user_character($char);
|
change_user_character($char->id);
|
||||||
|
|
||||||
flash('alert_character_list_1', ['success', 'Character <b>' . $name . '</b> created!']);
|
flash('alert_character_list_1', ['success', 'Character <b>' . $name . '</b> created!']);
|
||||||
redirect('/characters');
|
redirect('/characters');
|
||||||
|
|
|
@ -19,6 +19,6 @@ function profile_controller_show_get($id)
|
||||||
auth_only_and_must_have_character();
|
auth_only_and_must_have_character();
|
||||||
|
|
||||||
if (($char = char_find($id)) == false) router_error(999);
|
if (($char = char_find($id)) == false) router_error(999);
|
||||||
if (user('char_id') == $id) redirect('/profile');
|
if (user()->char_id == $id) redirect('/profile');
|
||||||
echo page('profile/show', ['char' => $char]);
|
echo page('profile/show', ['char' => $char]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ function world_controller_move_post()
|
||||||
$r = db_query(db_live(), 'UPDATE char_locations SET x = :x, y = :y WHERE char_id = :c', [
|
$r = db_query(db_live(), 'UPDATE char_locations SET x = :x, y = :y WHERE char_id = :c', [
|
||||||
':x' => $x,
|
':x' => $x,
|
||||||
':y' => $y,
|
':y' => $y,
|
||||||
':c' => user('char_id')
|
':c' => user()->char_id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($r === false) throw new Exception('Failed to move character. (wcmp)');
|
if ($r === false) throw new Exception('Failed to move character. (wcmp)');
|
||||||
|
|
|
@ -94,58 +94,51 @@ function set_cookie($name, $value, $expires)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current user's array from SESSION if it exists. Specify a key to get a specific value.
|
* Get the current user's object from SESSION if it exists.
|
||||||
*/
|
*/
|
||||||
function user($field = '')
|
function user(): User|false
|
||||||
{
|
{
|
||||||
if (empty($_SESSION['user'])) return false;
|
if (empty($_SESSION['user'])) return false;
|
||||||
if ($field === '') return $_SESSION['user'];
|
return unserialize($_SESSION['user']);
|
||||||
return $_SESSION['user'][$field] ?? false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the user has selected a character.
|
* Modify a field in the user session object. Returns true on success and false on failure.
|
||||||
*/
|
*/
|
||||||
function user_selected_char()
|
function modify_user_session(string $field, mixed $value): bool
|
||||||
{
|
{
|
||||||
return user('char_id') > 0 ? true : false;
|
$user = user();
|
||||||
|
if ($user === false || !property_exists($user, $field)) return false;
|
||||||
|
$user->{$field} = $value;
|
||||||
|
$_SESSION['user'] = serialize($user);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 the character object. If there
|
||||||
* or a specific field. If there is no character data, populate it.
|
* is no character data, populate it.
|
||||||
*/
|
*/
|
||||||
function char($field = '')
|
function char(): Character|false
|
||||||
{
|
{
|
||||||
// If there is no user, return false
|
|
||||||
if (empty($_SESSION['user'])) return false;
|
if (empty($_SESSION['user'])) return false;
|
||||||
|
if (empty($GLOBALS['char'])) $GLOBALS['char'] = serialize(user()->current_char());
|
||||||
if (empty($GLOBALS['char'])) {
|
return unserialize($GLOBALS['char']);
|
||||||
$GLOBALS['char'] = db_query(
|
|
||||||
db_live(),
|
|
||||||
"SELECT * FROM characters WHERE id = :c",
|
|
||||||
[':c' => user('char_id')]
|
|
||||||
)->fetchArray(SQLITE3_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($field === '') return $GLOBALS['char'];
|
|
||||||
return $GLOBALS['char'][$field] ?? false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shorthand to update the user's selected character. Returns true on success, false on failure. Database
|
* Update the user's selected character. Returns true on success, false on failure. Database
|
||||||
* is updated if the character ID is different from the current session.
|
* is updated if the character ID is different from the current session.
|
||||||
*/
|
*/
|
||||||
function change_user_character($char_id)
|
function change_user_character(int $char_id): bool
|
||||||
{
|
{
|
||||||
// If the character does not exist, return false
|
// If the character does not exist, return false
|
||||||
if (($char = char_find($char_id)) === false) return false;
|
if (($char = Character::find($char_id)) === false) return false;
|
||||||
$GLOBALS['char'] = $char;
|
$GLOBALS['char'] = serialize($char);
|
||||||
|
|
||||||
// If the character ID is different, update the session and database
|
// If the character ID is different, update the session and database
|
||||||
if ($_SESSION['user']['char_id'] !== $char_id) {
|
if (user()->char_id !== $char_id) {
|
||||||
$_SESSION['user']['char_id'] = $char_id;
|
modify_user_session('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]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -154,7 +147,7 @@ function change_user_character($char_id)
|
||||||
/**
|
/**
|
||||||
* Get a percent between two ints, rounded to the nearest whole number or return 0.
|
* Get a percent between two ints, rounded to the nearest whole number or return 0.
|
||||||
*/
|
*/
|
||||||
function percent($num, $denom, $precision = 4): int
|
function percent(float $num, float $denom, int $precision = 4): float
|
||||||
{
|
{
|
||||||
if ($denom === 0) return 0;
|
if ($denom === 0) return 0;
|
||||||
$p = ($num / $denom) * 100;
|
$p = ($num / $denom) * 100;
|
||||||
|
@ -164,20 +157,14 @@ function percent($num, $denom, $precision = 4): int
|
||||||
/**
|
/**
|
||||||
* Access the account wallet. On first execution it will populate $GLOBALS['wallet'] with the wallet data. This way
|
* 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
|
* 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.
|
* template. Will return false if the user or wallet does not exist.
|
||||||
*/
|
*/
|
||||||
function wallet($field = '')
|
function wallet(): Wallet|false
|
||||||
{
|
{
|
||||||
if (empty($GLOBALS['wallet'])) {
|
if (user() === false) return false;
|
||||||
$GLOBALS['wallet'] = db_query(
|
if (empty($GLOBALS['wallet'])) $w = user()->wallet();
|
||||||
db_live(),
|
if ($w === false) return false;
|
||||||
"SELECT * FROM wallets WHERE user_id = :u",
|
return $GLOBALS['wallet'] = $w;
|
||||||
[':u' => user('id')]
|
|
||||||
)->fetchArray(SQLITE3_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($field === '') return $GLOBALS['wallet'];
|
|
||||||
return $GLOBALS['wallet'][$field] ?? false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -191,7 +178,7 @@ function location($field = '')
|
||||||
$GLOBALS['location'] = db_query(
|
$GLOBALS['location'] = db_query(
|
||||||
db_live(),
|
db_live(),
|
||||||
"SELECT * FROM char_locations WHERE char_id = :c",
|
"SELECT * FROM char_locations WHERE char_id = :c",
|
||||||
[':c' => user('char_id')]
|
[':c' => user()->char_id]
|
||||||
)->fetchArray(SQLITE3_ASSOC);
|
)->fetchArray(SQLITE3_ASSOC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,3 +287,12 @@ function abb_num($num)
|
||||||
default => number_format($num, 0)
|
default => number_format($num, 0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if all keys of an array are numeric.
|
||||||
|
*/
|
||||||
|
function all_keys_numeric(array $a): bool
|
||||||
|
{
|
||||||
|
foreach (array_keys($a) as $k) if (!is_int($k)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -1,284 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
characters are the living, breathing entities that interact with the game world. They are inextricably linked to their
|
|
||||||
accounts, and are the primary means by which the character interacts with the game world. Separating the character from
|
|
||||||
the account allows for multiple characters to be associated with a single account, and to prevent concurrency issues
|
|
||||||
when performing auth checks on the database.
|
|
||||||
|
|
||||||
When creating a character, we want to init all of the related data tables; wallets, inventory, bank, etc.
|
|
||||||
|
|
||||||
When retrieving a character, we will get the tables as-needed, to prevent allocating more memory than we need.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const currently = [
|
|
||||||
0 => 'Exploring',
|
|
||||||
1 => 'In Town',
|
|
||||||
2 => 'In Combat',
|
|
||||||
4 => 'In Shop',
|
|
||||||
5 => 'In Inn'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a character. Only a user ID and a name are required. All other fields are optional. Pass a key-value array
|
|
||||||
* of overrides to set additional fields. A character's name must be unique, but this function does not check for
|
|
||||||
* that. Returns the created character's ID.
|
|
||||||
*/
|
|
||||||
function char_create($user_id, $name, $overrides = []): int
|
|
||||||
{
|
|
||||||
// Prep the data and merge in any overrides
|
|
||||||
$data = ['user_id' => $user_id, 'name' => $name];
|
|
||||||
if (!empty($overrides)) $data = array_merge($data, $overrides);
|
|
||||||
|
|
||||||
// Prep the fields for the query
|
|
||||||
$k = array_keys($data);
|
|
||||||
$f = implode(', ', $k);
|
|
||||||
$v = implode(', ', array_map(fn($x) => ":$x", $k));
|
|
||||||
|
|
||||||
// Create the character!
|
|
||||||
if (db_query(db_live(), "INSERT INTO characters ($f) VALUES ($v)", $data) === false) {
|
|
||||||
// @TODO: Log this error
|
|
||||||
throw new Exception('Failed to create character. (cc)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the character ID
|
|
||||||
return db_live()->lastInsertRowID();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a character's location record. A character's location is where they are in the game world. A character can only be
|
|
||||||
* in one location at a time. Can define a starting location for the character. Default state is 'Exploring'.
|
|
||||||
*/
|
|
||||||
function char_location_create($char_id, $x = 0, $y = 0, $currently = 0)
|
|
||||||
{
|
|
||||||
if (db_query(db_live(), "INSERT INTO char_locations (char_id, x, y, currently) VALUES (:p, :x, :y, :c)", [
|
|
||||||
':p' => $char_id,
|
|
||||||
':x' => $x,
|
|
||||||
':y' => $y,
|
|
||||||
':c' => $currently
|
|
||||||
]) === false) {
|
|
||||||
throw new Exception('Failed to create character location. (clc)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the character's gear table. A character's gear is where they store their equipped items.
|
|
||||||
* @TODO: implement initial gear
|
|
||||||
*/
|
|
||||||
function char_gear_create($char_id, $initialGear = [])
|
|
||||||
{
|
|
||||||
if (db_query(db_live(), "INSERT INTO char_gear (char_id) VALUES (:p)", [':p' => $char_id]) === false) {
|
|
||||||
throw new Exception('Failed to create character gear. (cgc)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a charcter by their ID or name. Returns the character's data as an associative array, or false if not found.
|
|
||||||
*/
|
|
||||||
function char_find($char_id)
|
|
||||||
{
|
|
||||||
$char = db_query(db_live(), "SELECT * FROM characters WHERE id = :id OR name = :id", [':id' => $char_id])->fetchArray(SQLITE3_ASSOC);
|
|
||||||
return $char === false ? false : $char;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count the number of characters associated with an account ID.
|
|
||||||
*/
|
|
||||||
function char_count($user_id): int
|
|
||||||
{
|
|
||||||
$count = db_query(db_live(), "SELECT COUNT(*) FROM characters WHERE user_id = :u", [':u' => $user_id])->fetchArray(SQLITE3_NUM);
|
|
||||||
if ($count === false) throw new Exception('Failed to count characters. (cc)');
|
|
||||||
return (int) $count[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a an array of id => [name, level] for all characters associated with an account ID.
|
|
||||||
*/
|
|
||||||
function char_list($user_id)
|
|
||||||
{
|
|
||||||
$stmt = db_query(db_live(), "SELECT id, name, level FROM characters WHERE user_id = :u", [':u' => $user_id]);
|
|
||||||
if ($stmt === false) throw new Exception('Failed to list characters. (cl)');
|
|
||||||
|
|
||||||
$characters = [];
|
|
||||||
while ($row = $stmt->fetchArray(SQLITE3_ASSOC)) {
|
|
||||||
$characters[$row['id']] = ['name' => $row['name'], 'level' => $row['level']];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $characters;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a character's location info by their character ID. Returns the location's data as an associative array.
|
|
||||||
*/
|
|
||||||
function char_get_location($char_id)
|
|
||||||
{
|
|
||||||
// Get the location
|
|
||||||
$location = db_query(db_live(), "SELECT * FROM char_locations WHERE char_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC);
|
|
||||||
if ($location === false) throw new Exception('Location not found. (cgl)');
|
|
||||||
return $location;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See if a character name exists.
|
|
||||||
*/
|
|
||||||
function char_name_exists($name)
|
|
||||||
{
|
|
||||||
return db_exists(db_live(), 'characters', 'name', $name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a character exists at a certain ID.
|
|
||||||
*/
|
|
||||||
function char_exists($char_id)
|
|
||||||
{
|
|
||||||
return db_exists(db_live(), 'characters', 'id', $char_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See if the given character belongs to the given user. Returns false if the character does not belong to the user,
|
|
||||||
* or if the character does not exist. Returns true if the character belongs to the user. Generally this function
|
|
||||||
* shouldn't return false, as it should be called after the character's existence is confirmed. If it does return false,
|
|
||||||
* it is likely due to user interference.
|
|
||||||
*/
|
|
||||||
function char_belongs_to_user($char_id, $user_id)
|
|
||||||
{
|
|
||||||
$char = db_query(
|
|
||||||
db_live(),
|
|
||||||
"SELECT 1 FROM characters WHERE id = :p AND user_id = :u",
|
|
||||||
[':p' => $char_id, ':u' => $user_id]
|
|
||||||
)->fetchArray(SQLITE3_ASSOC);
|
|
||||||
return $char !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a character by their ID. This will delete all associated data tables as well.
|
|
||||||
*/
|
|
||||||
function char_delete($char_id)
|
|
||||||
{
|
|
||||||
// Delete the character
|
|
||||||
if (db_query(db_live(), "DELETE FROM characters WHERE id = :p", [':p' => $char_id]) === false) {
|
|
||||||
throw new Exception('Failed to delete character. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get item IDs from the character's inventory
|
|
||||||
$items = db_query(db_live(), "SELECT item_id FROM char_inventory WHERE char_id = :p", [':p' => $char_id]);
|
|
||||||
// delete the character's inventory and items
|
|
||||||
while ($row = $items->fetchArray(SQLITE3_ASSOC)) {
|
|
||||||
if (db_query(db_live(), "DELETE FROM char_inventory WHERE char_id = :c", [':c' => $char_id]) === false) {
|
|
||||||
throw new Exception('Failed to delete character inventory. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (db_query(db_live(), "DELETE FROM items WHERE id = :p", [':p' => $row['id']]) === false) {
|
|
||||||
throw new Exception('Failed to delete character item slots. (cd)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the character's location
|
|
||||||
if (db_query(db_live(), "DELETE FROM char_locations WHERE char_id = :p", [':p' => $char_id]) === false) {
|
|
||||||
throw new Exception('Failed to delete character location. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the character's gear
|
|
||||||
if (db_query(db_live(), "DELETE FROM char_gear WHERE char_id = :p", [':p' => $char_id]) === false) {
|
|
||||||
throw new Exception('Failed to delete character gear. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the character's bank
|
|
||||||
if (db_query(db_live(), "DELETE FROM char_bank WHERE char_id = :p", [':p' => $char_id]) === false) {
|
|
||||||
throw new Exception('Failed to delete character bank. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete character's banked items
|
|
||||||
if (db_query(db_live(), "DELETE FROM char_banked_items WHERE char_id = :p", [':p' => $char_id]) === false) {
|
|
||||||
throw new Exception('Failed to delete character bank items. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the user's guild membership
|
|
||||||
if (db_query(db_live(), "DELETE FROM guild_members WHERE char_id = :p", [':p' => $char_id]) === false) {
|
|
||||||
throw new Exception('Failed to delete character guild membership. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the character was a guild leader, hand leadership to the next highest ranking member
|
|
||||||
$guild = db_query(db_live(), "SELECT id FROM guilds WHERE leader_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC);
|
|
||||||
if ($guild !== false) {
|
|
||||||
$members = db_query(db_live(), "SELECT char_id FROM guild_members WHERE guild_id = :p ORDER BY rank DESC", [':p' => $guild['id']]);
|
|
||||||
$newLeader = $members->fetchArray(SQLITE3_ASSOC);
|
|
||||||
if ($newLeader !== false) {
|
|
||||||
db_query(db_live(), "UPDATE guilds SET leader_id = :p WHERE id = :g", [':p' => $newLeader['char_id'], ':g' => $guild['id']]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of all pve fight IDs.
|
|
||||||
$pve = db_query(db_fights(), "SELECT id FROM pve WHERE char_id = :p", [':p' => $char_id]);
|
|
||||||
// Get a list of all pvp fight IDs.
|
|
||||||
$pvp = db_query(db_fights(), "SELECT id FROM pvp WHERE char1_id = :p OR char2_id = :p", [':p' => $char_id]);
|
|
||||||
|
|
||||||
// Delete all pve fights
|
|
||||||
while ($row = $pve->fetchArray(SQLITE3_ASSOC)) {
|
|
||||||
if (db_query(db_fights(), "DELETE FROM pve WHERE id = :p", [':p' => $row['id']]) === false) {
|
|
||||||
throw new Exception('Failed to delete pve fight. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (db_query(db_fights(), "DELETE FROM pve_logs WHERE fight_id = :p", [':p' => $row['id']]) === false) {
|
|
||||||
throw new Exception('Failed to delete pve fight logs. (cd)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all pvp fights
|
|
||||||
while ($row = $pvp->fetchArray(SQLITE3_ASSOC)) {
|
|
||||||
if (db_query(db_fights(), "DELETE FROM pvp WHERE id = :p", [':p' => $row['id']]) === false) {
|
|
||||||
throw new Exception('Failed to delete pvp fight. (cd)');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (db_query(db_fights(), "DELETE FROM pvp_logs WHERE fight_id = :p", [':p' => $row['id']]) === false) {
|
|
||||||
throw new Exception('Failed to delete pvp fight logs. (cd)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Award a character a title.
|
|
||||||
*/
|
|
||||||
function char_award_title($title_id, $char_id)
|
|
||||||
{
|
|
||||||
$r = db_query(
|
|
||||||
db_live(),
|
|
||||||
'INSERT INTO owned_titles (`title_id`, `char_id`) VALUES (:t, :c)',
|
|
||||||
[':t' => $title_id, ':c' => $char_id]
|
|
||||||
);
|
|
||||||
if ($r === false) throw new Exception("Failed to award $char_id the title $title_id. (cat)");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the character's title's info and award date. Will use the currently logged in character's ID if $char_id is 0.
|
|
||||||
*/
|
|
||||||
function char_get_title($char_id = 0)
|
|
||||||
{
|
|
||||||
$char = $char_id === 0 ? char() : char_find($char_id);
|
|
||||||
$title = title($char['title_id']);
|
|
||||||
|
|
||||||
$stmt = db_query(
|
|
||||||
db_live(),
|
|
||||||
'SELECT awarded FROM owned_titles WHERE char_id = :c AND title_id = :t LIMIT 1',
|
|
||||||
[':c' => $char['id'], ':t' => $char['title_id']]
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the query failed, send back an array with only an error.
|
|
||||||
if ($stmt === false) return ['error' => "owned titles query failed {$char['id']} (cat) (1)"];
|
|
||||||
|
|
||||||
$award = $stmt->fetchArray(SQLITE3_ASSOC);
|
|
||||||
|
|
||||||
// If no title, send back an empty array
|
|
||||||
if (!$award) return [];
|
|
||||||
|
|
||||||
$title['awarded'] = $award['awarded'];
|
|
||||||
return $title;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the character's user data.
|
|
||||||
*/
|
|
||||||
function char_get_user($char)
|
|
||||||
{
|
|
||||||
return user_find($char['user_id']);
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a user by username, email, or id.
|
|
||||||
*/
|
|
||||||
function user_find($user)
|
|
||||||
{
|
|
||||||
$result = db_query(db_auth(), "SELECT * FROM users WHERE username = :u OR email = :u OR id = :u", [':u' => $user]);
|
|
||||||
$user = $result->fetchArray(SQLITE3_ASSOC);
|
|
||||||
if (!$user) return false;
|
|
||||||
$result->finalize();
|
|
||||||
return $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a user with a username, email, and password. Optionally pass an auth level. This function will not check
|
|
||||||
* if the username or email already exists. It is up to the caller to check this before calling this function. It is
|
|
||||||
* also up to the caller to validate password strength. This function will hash the password with the PASSWORD_ARGON2ID
|
|
||||||
* algorithm.
|
|
||||||
*/
|
|
||||||
function user_create($username, $email, $password, $auth = 0)
|
|
||||||
{
|
|
||||||
return db_query(db_auth(), "INSERT INTO users (username, email, password, auth) VALUES (:u, :e, :p, :a)", [
|
|
||||||
':u' => $username,
|
|
||||||
':e' => $email,
|
|
||||||
':p' => password_hash($password, PASSWORD_ARGON2ID),
|
|
||||||
':a' => $auth
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a user by username, email, or id.
|
|
||||||
*/
|
|
||||||
function user_delete($user)
|
|
||||||
{
|
|
||||||
return db_query(db_auth(), "DELETE FROM users WHERE username = :u OR email = :u OR id = :u", [':u' => $user]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an account wallet. 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($user_id, $silver = -1, $starGems = -1)
|
|
||||||
{
|
|
||||||
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)');
|
|
||||||
}
|
|
||||||
}
|
|
262
src/models/character.php
Normal file
262
src/models/character.php
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
Characters are the living, breathing entities that interact with the game world. They are inextricably linked to their
|
||||||
|
accounts, and are the primary means by which the character interacts with the game world. Separating the character from
|
||||||
|
the account allows for multiple characters to be associated with a single account, and to prevent concurrency issues
|
||||||
|
when performing auth checks on the database.
|
||||||
|
|
||||||
|
When creating a character, we want to init all of the related data tables; wallets, inventory, bank, etc.
|
||||||
|
|
||||||
|
When retrieving a character, we will get the tables as-needed, to prevent allocating more memory than we need.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Character
|
||||||
|
{
|
||||||
|
public int $id;
|
||||||
|
public int $user_id;
|
||||||
|
public string $name;
|
||||||
|
public int $title_id;
|
||||||
|
public int $level;
|
||||||
|
public int $xp;
|
||||||
|
public int $xp_to_level;
|
||||||
|
public int $hp; // Health
|
||||||
|
public int $m_hp;
|
||||||
|
public int $mp; // Mana
|
||||||
|
public int $m_mp;
|
||||||
|
public int $tp; // Travel
|
||||||
|
public int $m_tp;
|
||||||
|
public int $pow; // Power
|
||||||
|
public int $acc; // Accuracy
|
||||||
|
public int $pen; // Penetration
|
||||||
|
public int $foc; // Focus
|
||||||
|
public int $tou; // Toughness
|
||||||
|
public int $arm; // Armor
|
||||||
|
public int $res; // Resist
|
||||||
|
public int $pre; // Precision
|
||||||
|
public int $fer; // Ferocity
|
||||||
|
public int $luck; // Luck
|
||||||
|
public int $inv_slots;
|
||||||
|
public int $att_points;
|
||||||
|
public string $bio;
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
foreach ($data as $k => $v) {
|
||||||
|
if (property_exists($this, $k)) $this->$k = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function find(int $id): Character|false
|
||||||
|
{
|
||||||
|
$q = db_query(
|
||||||
|
db_live(),
|
||||||
|
"SELECT * FROM characters WHERE id = :id OR name = :id COLLATE NOCASE",
|
||||||
|
[':id' => $id]
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(int $user_id, string $name, array $overrides = []): Character|false
|
||||||
|
{
|
||||||
|
// Prep the data and merge in any overrides
|
||||||
|
$data = ['user_id' => $user_id, 'name' => $name];
|
||||||
|
if (!empty($overrides)) $data = array_merge($data, $overrides);
|
||||||
|
|
||||||
|
// Prep the fields for the query
|
||||||
|
$k = array_keys($data);
|
||||||
|
$f = implode(', ', $k);
|
||||||
|
$v = implode(', ', array_map(fn($x) => ":$x", $k));
|
||||||
|
|
||||||
|
// Create the character!
|
||||||
|
if (db_query(db_live(), "INSERT INTO characters ($f) VALUES ($v)", $data) === false) {
|
||||||
|
// @TODO: Log this error
|
||||||
|
throw new Exception('Failed to create character. (cc)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the character ID
|
||||||
|
return Character::find(db_live()->lastInsertRowID());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an associated location row for this character.
|
||||||
|
*/
|
||||||
|
public function create_location(int $x = 0, int $y = 0, int $currently = 0): bool
|
||||||
|
{
|
||||||
|
$l = db_query(
|
||||||
|
db_live(),
|
||||||
|
"INSERT INTO char_locations (char_id, x, y, currently) VALUES (:i, :x, :y, :c)",
|
||||||
|
[':i' => $this->id, ':x' => $x, ':y' => $y, ':c' => $currently]
|
||||||
|
);
|
||||||
|
return $l !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an associated gear row for this character.
|
||||||
|
*/
|
||||||
|
public function create_gear(array $initialGear = []): bool
|
||||||
|
{
|
||||||
|
// @TODO implement initial gear
|
||||||
|
$g = db_query(db_live(), "INSERT INTO char_gear (char_id) VALUES (:i)", [':i' => $this->id]);
|
||||||
|
return $g !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a given character name exists already.
|
||||||
|
*/
|
||||||
|
public static function name_exists(string $name): bool
|
||||||
|
{
|
||||||
|
return db_exists(db_live(), 'characters', 'name', $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a character exists with the given ID.
|
||||||
|
*/
|
||||||
|
public static function exists(int $id): bool
|
||||||
|
{
|
||||||
|
return db_exists(db_live(), 'characters', 'id', $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a character belongs to the given user ID.
|
||||||
|
*/
|
||||||
|
public static function belongs_to(int $id, int $user_id): bool
|
||||||
|
{
|
||||||
|
$q = db_query(
|
||||||
|
db_live(),
|
||||||
|
"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)');
|
||||||
|
return $q->fetchArray(SQLITE3_ASSOC) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the character's user.
|
||||||
|
*/
|
||||||
|
public function user(): User|false
|
||||||
|
{
|
||||||
|
return User::find($this->user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Award this character a title.
|
||||||
|
*/
|
||||||
|
public function award_title(int $title_id): bool
|
||||||
|
{
|
||||||
|
$r = db_query(
|
||||||
|
db_live(),
|
||||||
|
'INSERT INTO owned_titles (`title_id`, `char_id`) VALUES (:t, :i)',
|
||||||
|
[':t' => $title_id, ':i' => $this->id]
|
||||||
|
);
|
||||||
|
return $r !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a character by the ID. Deletes many rows of data from many tables; items, bank, PvE and PvP w/ logs, etc
|
||||||
|
*/
|
||||||
|
public static function delete(int $id)
|
||||||
|
{
|
||||||
|
// Delete the character
|
||||||
|
if (db_query(db_live(), "DELETE FROM characters WHERE id = :p", [':p' => $id]) === false) {
|
||||||
|
throw new Exception('Failed to delete character. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get item IDs from the character's inventory
|
||||||
|
$items = db_query(db_live(), "SELECT item_id FROM char_inventory WHERE char_id = :p", [':p' => $id]);
|
||||||
|
// delete the character's inventory and items
|
||||||
|
while ($row = $items->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
if (db_query(db_live(), "DELETE FROM char_inventory WHERE char_id = :c", [':c' => $id]) === false) {
|
||||||
|
throw new Exception('Failed to delete character inventory. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db_query(db_live(), "DELETE FROM items WHERE id = :p", [':p' => $row['id']]) === false) {
|
||||||
|
throw new Exception('Failed to delete character item slots. (C::d)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the character's location
|
||||||
|
if (db_query(db_live(), "DELETE FROM char_locations WHERE char_id = :p", [':p' => $id]) === false) {
|
||||||
|
throw new Exception('Failed to delete character location. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the character's gear
|
||||||
|
if (db_query(db_live(), "DELETE FROM char_gear WHERE char_id = :p", [':p' => $id]) === false) {
|
||||||
|
throw new Exception('Failed to delete character gear. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the character's bank
|
||||||
|
if (db_query(db_live(), "DELETE FROM char_bank WHERE char_id = :p", [':p' => $id]) === false) {
|
||||||
|
throw new Exception('Failed to delete character bank. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete character's banked items
|
||||||
|
if (db_query(db_live(), "DELETE FROM char_banked_items WHERE char_id = :p", [':p' => $id]) === false) {
|
||||||
|
throw new Exception('Failed to delete character bank items. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the user's guild membership
|
||||||
|
if (db_query(db_live(), "DELETE FROM guild_members WHERE char_id = :p", [':p' => $id]) === false) {
|
||||||
|
throw new Exception('Failed to delete character guild membership. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the character was a guild leader, hand leadership to the next highest ranking member
|
||||||
|
$guild = db_query(db_live(), "SELECT id FROM guilds WHERE leader_id = :p", [':p' => $id])->fetchArray(SQLITE3_ASSOC);
|
||||||
|
if ($guild !== false) {
|
||||||
|
$members = db_query(db_live(), "SELECT char_id FROM guild_members WHERE guild_id = :p ORDER BY rank DESC", [':p' => $guild['id']]);
|
||||||
|
$newLeader = $members->fetchArray(SQLITE3_ASSOC);
|
||||||
|
if ($newLeader !== false) {
|
||||||
|
db_query(db_live(), "UPDATE guilds SET leader_id = :p WHERE id = :g", [':p' => $newLeader['char_id'], ':g' => $guild['id']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of all pve fight IDs.
|
||||||
|
$pve = db_query(db_fights(), "SELECT id FROM pve WHERE char_id = :p", [':p' => $id]);
|
||||||
|
// Get a list of all pvp fight IDs.
|
||||||
|
$pvp = db_query(db_fights(), "SELECT id FROM pvp WHERE char1_id = :p OR char2_id = :p", [':p' => $id]);
|
||||||
|
|
||||||
|
// Delete all pve fights
|
||||||
|
while ($row = $pve->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
if (db_query(db_fights(), "DELETE FROM pve WHERE id = :p", [':p' => $row['id']]) === false) {
|
||||||
|
throw new Exception('Failed to delete pve fight. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db_query(db_fights(), "DELETE FROM pve_logs WHERE fight_id = :p", [':p' => $row['id']]) === false) {
|
||||||
|
throw new Exception('Failed to delete pve fight logs. (C::d)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all pvp fights
|
||||||
|
while ($row = $pvp->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
if (db_query(db_fights(), "DELETE FROM pvp WHERE id = :p", [':p' => $row['id']]) === false) {
|
||||||
|
throw new Exception('Failed to delete pvp fight. (C::d)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db_query(db_fights(), "DELETE FROM pvp_logs WHERE fight_id = :p", [':p' => $row['id']]) === false) {
|
||||||
|
throw new Exception('Failed to delete pvp fight logs. (C::d)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the character's current title.
|
||||||
|
*/
|
||||||
|
public function title(): array|false
|
||||||
|
{
|
||||||
|
$t = title($this->title_id);
|
||||||
|
|
||||||
|
$q = db_query(
|
||||||
|
db_live(),
|
||||||
|
'SELECT awarded FROM owned_titles WHERE char_id = :c AND title_id = :t LIMIT 1',
|
||||||
|
[':c' => $this->id, ':t' => $this->title_id]
|
||||||
|
);
|
||||||
|
if ($q === false) throw new Exception('Failed to query title. (C::t)');
|
||||||
|
|
||||||
|
$a = $q->fetchArray(SQLITE3_ASSOC);
|
||||||
|
if ($a === false) return false;
|
||||||
|
|
||||||
|
$t['awarded'] = $a['awarded']; // add the awarded date to the title info
|
||||||
|
return $t;
|
||||||
|
}
|
||||||
|
}
|
180
src/models/user.php
Normal file
180
src/models/user.php
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a User from the auth database. Contains auth-related info and handles meta-level state.
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Auto-incrementing unique ID.
|
||||||
|
*/
|
||||||
|
public int $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A presumably unique username.
|
||||||
|
*/
|
||||||
|
public string $username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A presumably unique email address.
|
||||||
|
*/
|
||||||
|
public string $email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user's hashed password; private to protect it from inspection.
|
||||||
|
*/
|
||||||
|
private string $password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth level; see Auth to check how permissions work.
|
||||||
|
*/
|
||||||
|
public int $auth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently selected character's ID.
|
||||||
|
*/
|
||||||
|
public int $char_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many characters the user can have.
|
||||||
|
*/
|
||||||
|
public int $char_slots;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this account was created (registered date).
|
||||||
|
*/
|
||||||
|
public DateTime $created;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the account was last logged in to.
|
||||||
|
*/
|
||||||
|
public DateTime $last_login;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate a User object with data; assumes you are passing the associatve array from SQLite directly.
|
||||||
|
*/
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
foreach ($data as $k => $v) {
|
||||||
|
if (property_exists($this, $k)) {
|
||||||
|
$this->$k = in_array($k, ['created', 'last_login']) ? new DateTime($v) : $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a user by their username, email or ID. Case-nonsensitive. Throws an Exception when the query is
|
||||||
|
* badly formed. Returns false if no user is found. Returns a User on success.
|
||||||
|
*/
|
||||||
|
public static function find(string|int $identifier): User|false
|
||||||
|
{
|
||||||
|
$r = db_query(
|
||||||
|
db_auth(),
|
||||||
|
"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
|
||||||
|
$u = $r->fetchArray(SQLITE3_ASSOC);
|
||||||
|
if ($u === false) return false; // no user found
|
||||||
|
return new User($u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new user row in the database. This function does not check for the unique-ness or validity
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
return db_query(db_auth(), "INSERT INTO users (username, email, password, auth) VALUES (:u, :e, :p, :a)", [
|
||||||
|
':u' => $username,
|
||||||
|
':e' => $email,
|
||||||
|
':p' => password_hash($password, PASSWORD_ARGON2ID),
|
||||||
|
':a' => $auth
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check $ref against the user's password.
|
||||||
|
*/
|
||||||
|
public function check_password(string $ref): bool
|
||||||
|
{
|
||||||
|
return password_verify($ref, $this->password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given username exists.
|
||||||
|
*/
|
||||||
|
public static function username_exists(string $username): bool
|
||||||
|
{
|
||||||
|
return db_exists(db_auth(), 'users', 'username', $username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given email exists.
|
||||||
|
*/
|
||||||
|
public static function email_exists(string $email): bool
|
||||||
|
{
|
||||||
|
return db_exists(db_auth(), 'users', 'email', $email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a user by their username, email, or id.
|
||||||
|
*/
|
||||||
|
public static function delete(string|int $identifier): SQLite3Result|false
|
||||||
|
{
|
||||||
|
return db_query(
|
||||||
|
db_auth(),
|
||||||
|
"DELETE FROM users WHERE username = :i OR email = :i OR id = :i",
|
||||||
|
[':i' => $identifier]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a count of how many characters this user has.
|
||||||
|
*/
|
||||||
|
public function char_count(): int
|
||||||
|
{
|
||||||
|
$c = db_query(
|
||||||
|
db_live(),
|
||||||
|
"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)');
|
||||||
|
return (int) $c[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a an array of id => [name, level] for all characters associated with this user ID. Returns false
|
||||||
|
* if there are no characters.
|
||||||
|
*/
|
||||||
|
public function char_list(): array|false
|
||||||
|
{
|
||||||
|
$q = db_query(db_live(), "SELECT id, name, level FROM characters WHERE user_id = ?", [$this->id]);
|
||||||
|
if ($q === false) throw new Exception('Failed to list characters. (U->cl)');
|
||||||
|
|
||||||
|
$c = [];
|
||||||
|
while ($row = $q->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$c[$row['id']] = ['name' => $row['name'], 'level' => $row['level']];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
52
src/models/wallet.php
Normal file
52
src/models/wallet.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Wallet
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $user_id,
|
||||||
|
public int $silver,
|
||||||
|
public int $stargem
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function find(int $user_id): Wallet|false
|
||||||
|
{
|
||||||
|
$r = db_query(db_live(), 'SELECT * FROM wallets WHERE user_id = ?', [$user_id]);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
return 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
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a certain amount of currency to the user's wallet.
|
||||||
|
*/
|
||||||
|
public function give(Currency $c, int $amt): SQLite3Result|false
|
||||||
|
{
|
||||||
|
$cs = $c->string(true);
|
||||||
|
$new = $this->{$cs} + $amt;
|
||||||
|
return db_query(db_live(), "UPDATE wallets SET $cs = ? WHERE user_id = ?", [$new, $this->user_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a certain amount of currency from the user's wallet.
|
||||||
|
*/
|
||||||
|
public function take(Currency $c, int $amt): SQLite3Result|false
|
||||||
|
{
|
||||||
|
$cs = $c->string(true);
|
||||||
|
$new = $this->{$cs} - $amt;
|
||||||
|
return db_query(db_live(), "UPDATE wallets SET $cs = ? WHERE user_id = ?", [$new < 0 ? 0 : $new, $this->user_id]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given username already exists.
|
|
||||||
*/
|
|
||||||
function auth_username_exists($username)
|
|
||||||
{
|
|
||||||
return db_exists(db_auth(), 'users', 'username', $username);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given email already exists.
|
|
||||||
*/
|
|
||||||
function auth_email_exists($email)
|
|
||||||
{
|
|
||||||
return db_exists(db_auth(), 'users', 'email', $email);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for a user session. If $_SESSION['user'] already exists, return early. If not, check for a remember me
|
* Check for a user session. If $_SESSION['user'] already exists, return early. If not, check for a remember me
|
||||||
* cookie. If a remember me cookie exists, validate the session and set $_SESSION['user'].
|
* cookie. If a remember me cookie exists, validate the session and set $_SESSION['user'].
|
||||||
|
@ -27,10 +11,8 @@ function auth_check()
|
||||||
if (isset($_COOKIE['remember_me'])) {
|
if (isset($_COOKIE['remember_me'])) {
|
||||||
$session = session_validate($_COOKIE['remember_me']);
|
$session = session_validate($_COOKIE['remember_me']);
|
||||||
if ($session === true) {
|
if ($session === true) {
|
||||||
$user = user_find($session['user_id']);
|
$user = User::find($session['user_id']);
|
||||||
unset($user['password']);
|
$_SESSION['user'] = serialize($user);
|
||||||
$_SESSION['user'] = user_find($session['user_id']);
|
|
||||||
$_SESSION['char'] = char_find($user['char_id']);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,17 +44,21 @@ function guest_only()
|
||||||
function must_have_character()
|
function must_have_character()
|
||||||
{
|
{
|
||||||
// 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 (user()->char_id !== 0) {
|
||||||
char();
|
char();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no characters, redirect to create first
|
// if no characters, redirect to create first
|
||||||
if (char_count(user('id')) === 0) redirect('/character/create-first');
|
if (user()->char_count() === 0) redirect('/character/create-first');
|
||||||
|
|
||||||
// if no character selected, select the first one
|
// if no character selected, select the first one
|
||||||
if ($_SESSION['user']['char_id'] === 0) {
|
if (user()->char_id === 0) {
|
||||||
$char = db_query(db_live(), 'SELECT * FROM characters WHERE user_id = :u ORDER BY id ASC LIMIT 1', [':u' => user('id')])->fetchArray(SQLITE3_ASSOC);
|
$char = db_query(
|
||||||
|
db_live(),
|
||||||
|
'SELECT * FROM characters WHERE user_id = :u ORDER BY id ASC LIMIT 1',
|
||||||
|
[':u' => user()->id]
|
||||||
|
)->fetchArray(SQLITE3_ASSOC);
|
||||||
change_user_character($char['id']);
|
change_user_character($char['id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,14 @@ function c_left_nav()
|
||||||
return render('components/left_nav');
|
return render('components/left_nav');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the right sidebar menu.
|
||||||
|
*/
|
||||||
|
function c_right_nav()
|
||||||
|
{
|
||||||
|
return render('components/right_nav', ['c' => char()]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the debug query log.
|
* Render the debug query log.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -57,10 +57,13 @@ function db_blueprints()
|
||||||
* Take a SQLite3 database connection, a query string, and an array of parameters. Prepare the query and
|
* Take a SQLite3 database connection, a query string, and an array of parameters. Prepare the query and
|
||||||
* bind the parameters with proper type casting. Then execute the query and return the result.
|
* bind the parameters with proper type casting. Then execute the query and return the result.
|
||||||
*/
|
*/
|
||||||
function db_query($db, $query, $params = [])
|
function db_query(SQLite3 $db, string $query, array $params = []): SQLite3Result|false
|
||||||
{
|
{
|
||||||
|
$p = strpos($query, '?') !== false; // are generic placeholders?
|
||||||
$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 $k => $v) $stmt->bindValue($p ? $k + 1 : $k, $v, getSQLiteType($v));
|
||||||
|
}
|
||||||
$start = microtime(true);
|
$start = microtime(true);
|
||||||
$r = $stmt->execute();
|
$r = $stmt->execute();
|
||||||
db_log($query, microtime(true) - $start);
|
db_log($query, microtime(true) - $start);
|
||||||
|
@ -79,10 +82,10 @@ function db_exec($db, $query)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a SQLite3 database connection, a column name, and a value. Execute a COUNT query to see if the value
|
* Take a SQLite3 database connection, a column name, and a value. Execute a SELECT query to see if the value
|
||||||
* exists in the column. Return true if the value exists, false otherwise.
|
* exists in the column. Return true if the value exists, false otherwise.
|
||||||
*/
|
*/
|
||||||
function db_exists($db, $table, $column, $value, $caseInsensitive = true)
|
function db_exists(SQLite3 $db, string $table, string $column, mixed $value, bool $caseInsensitive = true): bool
|
||||||
{
|
{
|
||||||
if ($caseInsensitive) {
|
if ($caseInsensitive) {
|
||||||
$query = "SELECT 1 FROM $table WHERE $column = :v COLLATE NOCASE LIMIT 1";
|
$query = "SELECT 1 FROM $table WHERE $column = :v COLLATE NOCASE LIMIT 1";
|
||||||
|
|
25
src/util/enums.php
Normal file
25
src/util/enums.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
enum Currently {
|
||||||
|
case Exploring;
|
||||||
|
case InTown;
|
||||||
|
case InCombat;
|
||||||
|
case InShop;
|
||||||
|
case InInn;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Currency {
|
||||||
|
case Silver;
|
||||||
|
case StarGem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert currency to it's human readable string or DB column name.
|
||||||
|
*/
|
||||||
|
public function string(bool $db = false): string
|
||||||
|
{
|
||||||
|
return match($this) {
|
||||||
|
Currency::Silver => $db ? 'silver' : 'Silver',
|
||||||
|
Currency::StarGem => $db ? 'stargem' : 'Star Gem'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
<div id="char-bar">
|
|
||||||
<div class="container">
|
|
||||||
<div>
|
|
||||||
<img class="icon" src="/assets/img/icons/user1.png" alt="User">
|
|
||||||
<?= $char['name'] ?> <span class="badge ml-2 tooltip-hover" data-tooltip-content="Level"><?= $char['level'] ?></span>
|
|
||||||
<?php if ($char['att_points'] > 0): ?>
|
|
||||||
<span class="ui button primary badge ml-2 tooltip-hover" data-tooltip-content="Attribute Points"><?= $char['att_points'] ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="char-meter">
|
|
||||||
<div class="hp" style="width: <?= percent($char['hp'], $char['m_hp']) ?>%"></div>
|
|
||||||
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Health<br><?= $char['hp'] ?> / <?= $char['m_hp'] ?>"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="char-meter">
|
|
||||||
<div class="mp" style="width: <?= percent($char['mp'], $char['m_mp']) ?>%"></div>
|
|
||||||
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Mana<br><?= $char['mp'] ?> / <?= $char['m_mp'] ?>"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="char-meter">
|
|
||||||
<div class="tp" style="width: <?= percent($char['tp'], $char['m_tp']) ?>%"></div>
|
|
||||||
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Travel Points<br><?= $char['tp'] ?> / <?= $char['m_tp'] ?>"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<?= wallet('silver') ?> Silver
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="radio-block <?= $id === user('char_id') ? 'active' : '' ?>">
|
<div class="radio-block <?= $id === user()->char_id ? 'active' : '' ?>">
|
||||||
<input type="radio" name="char_id" value="<?= $id ?>" id="char_<?= $id ?>"<?= $id === user('char_id') ? 'disabled' : '' ?>>
|
<input type="radio" name="char_id" value="<?= $id ?>" id="char_<?= $id ?>"<?= $id === user()->char_id ? 'disabled' : '' ?>>
|
||||||
<label for="char_<?= $id ?>">
|
<label for="char_<?= $id ?>">
|
||||||
<?= $char['name'] ?>
|
<?= $char['name'] ?>
|
||||||
<span class="badge"><?= $char['level'] ?></span>
|
<span class="badge"><?= $char['level'] ?></span>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<h4>Stats</h4>
|
<h4>Stats</h4>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="cell"><span class="label">Max HP</span> <?= abb_num($char['m_hp']) ?></div>
|
<div class="cell"><span class="label">Max HP</span> <?= abb_num($char->m_hp) ?></div>
|
||||||
<div class="cell"><span class="label">Max MP</span> <?= abb_num($char['m_mp']) ?></div>
|
<div class="cell"><span class="label">Max MP</span> <?= abb_num($char->m_mp) ?></div>
|
||||||
<div class="cell"><span class="label">Power</span> <?= abb_num($char['pow']) ?></div>
|
<div class="cell"><span class="label">Power</span> <?= abb_num($char->pow) ?></div>
|
||||||
<div class="cell"><span class="label">Accuracy</span> <?= abb_num($char['acc']) ?></div>
|
<div class="cell"><span class="label">Accuracy</span> <?= abb_num($char->acc) ?></div>
|
||||||
<div class="cell"><span class="label">Penetration</span> <?= abb_num($char['pen']) ?></div>
|
<div class="cell"><span class="label">Penetration</span> <?= abb_num($char->pen) ?></div>
|
||||||
<div class="cell"><span class="label">Focus</span> <?= abb_num($char['foc']) ?></div>
|
<div class="cell"><span class="label">Focus</span> <?= abb_num($char->foc) ?></div>
|
||||||
<div class="cell"><span class="label">Toughness</span> <?= abb_num($char['tou']) ?></div>
|
<div class="cell"><span class="label">Toughness</span> <?= abb_num($char->tou) ?></div>
|
||||||
<div class="cell"><span class="label">Armor</span> <?= abb_num($char['arm']) ?></div>
|
<div class="cell"><span class="label">Armor</span> <?= abb_num($char->arm) ?></div>
|
||||||
<div class="cell"><span class="label">Resist</span> <?= abb_num($char['res']) ?></div>
|
<div class="cell"><span class="label">Resist</span> <?= abb_num($char->res) ?></div>
|
||||||
<div class="cell"><span class="label">Precision</span> <?= abb_num($char['pre']) ?></div>
|
<div class="cell"><span class="label">Precision</span> <?= abb_num($char->pre) ?></div>
|
||||||
<div class="cell"><span class="label">Ferocity</span> <?= abb_num($char['fer']) ?></div>
|
<div class="cell"><span class="label">Ferocity</span> <?= abb_num($char->fer) ?></div>
|
||||||
<div class="cell"><span class="label">Luck</span> <?= abb_num($char['luck']) ?></div>
|
<div class="cell"><span class="label">Luck</span> <?= abb_num($char->luck) ?></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<div id="character" class="box">
|
||||||
|
<div class="name">
|
||||||
|
<?= $c->name ?>
|
||||||
|
<?php if ($c->att_points > 0): ?>
|
||||||
|
<span class="ui button primary badge ml-2 tooltip-hover" data-tooltip-content="Attribute Points"><?= $c->att_points ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
L<?= $c->level ?> <?= $c->title()['name'] ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<?= wallet()->silver ?> s
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="char-meter">
|
||||||
|
<div class="hp" style="width: <?= percent($c->hp, $c->m_hp) ?>%"></div>
|
||||||
|
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Health<br><?= $c->hp ?> / <?= $c->m_hp ?>"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="char-meter">
|
||||||
|
<div class="mp" style="width: <?= percent($c->mp, $c->m_mp) ?>%"></div>
|
||||||
|
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Mana<br><?= $c->mp ?> / <?= $c->m_mp ?>"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="char-meter">
|
||||||
|
<div class="tp" style="width: <?= percent($c->tp, $c->m_tp) ?>%"></div>
|
||||||
|
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Travel Points<br><?= $c->tp ?> / <?= $c->m_tp ?>"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<?php if (user()): ?>
|
<?php if (user()): ?>
|
||||||
<p>Welcome, <?= user('username') ?></p>
|
<p>Welcome, <?= user()->username ?></p>
|
||||||
<?= c_logout_button() ?>
|
<?= c_logout_button() ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<a class="ui button primary" href="/auth/login">Login</a>
|
<a class="ui button primary" href="/auth/login">Login</a>
|
||||||
|
@ -24,11 +24,9 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<?= c_char_bar(user('char_id')) ?>
|
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<aside id="left">
|
<aside id="left">
|
||||||
<?php if (user() && user_selected_char()) echo c_left_nav($activeTab ?? 0); ?>
|
<?php if (user() && user()->char_id > 0) echo c_left_nav($activeTab ?? 0); ?>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div id="center">
|
<div id="center">
|
||||||
|
@ -36,11 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside id="right">
|
<aside id="right">
|
||||||
<?php if (user() && user_selected_char()): ?>
|
<?php if (user() && user()->char_id > 0) echo c_right_nav(); ?>
|
||||||
<div class="box">
|
|
||||||
@TODO
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</aside>
|
</aside>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<section>
|
<section>
|
||||||
<h1>Characters <span class="badge"><?= count($chars) . '/' . user('char_slots') ?></span></h1>
|
<h1>Characters <span class="badge"><?= count($chars) . '/' . user()->char_slots ?></span></h1>
|
||||||
<?php
|
<?php
|
||||||
if (($f = flash('alert_character_list_1')) !== false) echo c_alert($f[0], $f[1]);
|
if (($f = flash('alert_character_list_1')) !== false) echo c_alert($f[0], $f[1]);
|
||||||
|
|
||||||
|
@ -23,10 +23,10 @@
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php if (user('char_slots') > count($chars)): ?>
|
<?php if (user()->char_slots > count($chars)): ?>
|
||||||
<section>
|
<section>
|
||||||
<h2>Create a new character</h2>
|
<h2>Create a new character</h2>
|
||||||
<?php $num_slots_left = user('char_slots') - count($chars); ?>
|
<?php $num_slots_left = user()->char_slots - count($chars); ?>
|
||||||
<p>You have <b><?= $num_slots_left ?> <?= $num_slots_left === 1 ? 'slot' : 'slots' ?></b> left.</p>
|
<p>You have <b><?= $num_slots_left ?> <?= $num_slots_left === 1 ? 'slot' : 'slots' ?></b> left.</p>
|
||||||
<form action="/character/create" method="post">
|
<form action="/character/create" method="post">
|
||||||
<input type="hidden" name="csrf" value="<?= csrf() ?>">
|
<input type="hidden" name="csrf" value="<?= csrf() ?>">
|
||||||
|
|
|
@ -4,5 +4,5 @@
|
||||||
<a href="/auth/register" class="ui button secondary">Register</a>
|
<a href="/auth/register" class="ui button secondary">Register</a>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
<p>Welcome, <?= user('username') ?>!</p>
|
<p>Welcome, <?= user()->username ?>!</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<section class="profile">
|
<section class="profile">
|
||||||
<header>
|
<header>
|
||||||
<h1><?= char('name') ?></h1>
|
<h1><?= char()->name ?></h1>
|
||||||
<h3>Level <?= char('level') ?> <?= char_get_title()['name'] ?></h3>
|
<h3>Level <?= char()->level ?> <?= char()->title()['name'] ?></h3>
|
||||||
<h4>You</h4>
|
<h4>You</h4>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<section class="right">
|
<section class="right">
|
||||||
<div class="bio">
|
<div class="bio">
|
||||||
<h4>Biography</h4>
|
<h4>Biography</h4>
|
||||||
<?= char('bio') ?>
|
<?= char()->bio ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gear">
|
<div class="gear">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<header>
|
<header>
|
||||||
<h1><?= $char['name'] ?></h1>
|
<h1><?= $char['name'] ?></h1>
|
||||||
<h3>Level <?= $char['level'] ?> <?= char_get_title($char['id'])['name'] ?></h3>
|
<h3>Level <?= $char['level'] ?> <?= char_get_title($char['id'])['name'] ?></h3>
|
||||||
<h4>owned by <?= $char['user_id'] === user('id') ? 'you' : char_get_user($char)['username'] ?></h4>
|
<h4>owned by <?= $char['user_id'] === user()->id ? 'you' : char_get_user($char)['username'] ?></h4>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user