Compare commits

...

2 Commits

24 changed files with 712 additions and 135 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -74,6 +74,19 @@ body {
border-color: #32373E #24282D #212429; border-color: #32373E #24282D #212429;
} }
} }
&.danger {
background-color: #e57373;
background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(139, 0, 0, 0.1));
box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
border: 1px solid;
border-color: #d32f2f #c62828 #b71c1c;
&:hover {
background-color: #d95c5c;
border-color: #b71c1c #a52727 #8e1f1f;
}
}
} }
header { header {
@ -150,7 +163,7 @@ footer {
height: 34px; height: 34px;
color: white; color: white;
gap: 1rem; gap: 1rem;
background-image: url('/assets/img/deco-bar2.jpg'); background-image: url('/assets/img/bar.jpg');
& > div { & > div {
display: flex; display: flex;
@ -169,6 +182,11 @@ span.badge {
color: #111111; color: #111111;
border-radius: 0.25rem; border-radius: 0.25rem;
padding: 0.1rem 0.25rem; padding: 0.1rem 0.25rem;
&.dark {
background-color: #444c55;
color: white;
}
} }
.my-1 { margin-bottom: 0.25rem; margin-top: 0.25rem; } .my-1 { margin-bottom: 0.25rem; margin-top: 0.25rem; }
@ -258,3 +276,70 @@ span.badge {
top: 0; top: 0;
left: 0; left: 0;
} }
#debug-query-log {
padding: 2rem;
font-size: 14px;
color: #666;
font-family: monospace;
}
#center section {
&:not(:last-child) {
padding-bottom: 1rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin-bottom: 1rem;
}
}
h1:has(.badge), h2:has(.badge), h3:has(.badge), h4:has(.badge), h5:has(.badge), h6:has(.badge) {
display: flex;
align-items: center;
& > .badge {
margin-left: 0.5rem;
}
}
.alert {
position: relative;
min-height: 1rem;
margin: 1rem 0;
background: #f8f8f9;
padding: 0.5rem 1rem;
line-height: 1.4285rem;
color: rgba(0, 0, 0, .87);
transition: opacity .1s ease, color .1s ease, background .1s ease, box-shadow .1s ease;
border-radius: .28571429rem;
box-shadow: 0 0 0 1px rgba(34, 36, 38, .22) inset, 0 0 0 0 transparent;
&.success {
background-color: #f0f9eb;
color: #2c662d;
border-color: #b3dc9d;
}
&.danger {
background-color: #f9e9eb;
color: #9f3a38;
border-color: #e0b4b4;
}
&.warning {
background-color: #fff8e1;
color: #573a08;
border-color: #f9e79f;
}
&.info {
background-color: #f0f9fb;
color: #2c7fba;
border-color: #b3d7f9;
}
&.dark {
background-color: #f0f0f0;
color: #2c2c2c;
border-color: #b3b3b3;
}
}

View File

@ -21,3 +21,124 @@
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
} }
/*
.radio-block {
& > input[type="radio"] {
display: none;
}
& > label {
cursor: pointer;
display: inline-block;
border: none;
font-size: 1rem;
background: #f7f8fa linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1));
box-shadow: 0 1px 0 1px rgba(255, 255, 255, 0.3) inset, 0 0 0 1px #adb2bb inset;
color: #111111;
padding: 0.5rem 1rem 0.5rem;
text-align: center;
border-radius: 3px;
user-select: none;
text-decoration: none;
transition: opacity 0.1s ease, background-color 0.1s ease, color 0.1s ease, background 0.1s ease;
-webkit-tap-highlight-color: transparent;
&:hover {
background-color: #e0e0e0;
background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1));
box-shadow: 0 1px 0 1px rgba(255, 255, 255, 0.3) inset, 0 0 0 1px #adb2bb inset;
color: rgba(0, 0, 0, 0.8);
}
& > .badge {
background-color: #444c55;
color: white;
}
}
&.active > label {
background-color: #444c55;
color: #ffffff;
background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.1));
border: 1px solid;
border-color: #3D444C #2F353B #2C3137;
box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
& > .badge {
background-color: #f7f8fa;
color: #111111;
}
}
/* When the radio button is checked, change the background color of the label
& > input[type="radio"]:checked + label {
background-color: #5a9bd4;
background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(60, 100, 150, 0.1));
box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
border: 1px solid;
border-color: #4a8ab0 #3a7a9c #2a6a88;
}
/* When the radio button is disabled, show a normal cursor
& > input[type="radio"]:disabled + label {
cursor: default;
}
}
*/
.radio-block-2 {
& > input[type="radio"] {
display: none;
}
& > label {
cursor: pointer;
display: block;
}
}
.character-select > .radio-block {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 0.15rem;
& > input[type="radio"] {
display: none;
}
& > label {
display: flex;
align-items: center;
width: 100%;
border-radius: 0.15rem;
cursor: pointer;
transition: color, background-color 0.2s ease;
padding: 0.5rem;
&:hover {
background-color: black;
color: white;
}
& > .badge {
margin-left: 0.25rem;
}
}
&.active > label {
background-color: black;
color: white;
}
/* When the radio button is checked, change the background color of the label */
& > input[type="radio"]:checked + label {
background-color: #f4cc67;
color: #111111;
}
/* When the radio button is disabled, show a normal cursor */
& > input[type="radio"]:disabled + label {
cursor: default;
}
}

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -28,10 +28,11 @@ router_post($r, '/auth/logout', 'auth_controller_logout_post');
/* /*
Characters Characters
*/ */
router_get($r, '/characters', 'char_controller_select_get'); router_get($r, '/characters', 'char_controller_list_get');
router_post($r, '/character/create', 'char_controller_create_post'); router_post($r, '/characters', 'char_controller_list_post');
router_post($r, '/character/select', 'auth_controller_change_character_post');
router_get($r, '/character/create-first', 'char_controller_create_first_get'); router_get($r, '/character/create-first', 'char_controller_create_first_get');
router_post($r, '/character/create', 'char_controller_create_post');
router_post($r, '/character/delete', 'char_controller_delete_post');
/* /*
Router Router

View File

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

View File

@ -19,7 +19,7 @@ function c_logout_button(): string
*/ */
function c_char_bar(): string function c_char_bar(): string
{ {
if (!char()) return ''; if (char() === false) return '';
return render('components/char_bar', ['char' => char()]); return render('components/char_bar', ['char' => char()]);
} }
@ -30,3 +30,35 @@ function c_left_nav(int $activeTab): string
{ {
return render('components/left_nav', ['activeTab' => $activeTab]); return render('components/left_nav', ['activeTab' => $activeTab]);
} }
/**
* Render the debug query log.
*/
function c_debug_query_log(): string
{
return render('components/debug_query_log');
}
/**
* Render the character select radio buttons.
*/
function c_char_select_box(int $id, array $char): string
{
return render('components/char_select_box', ['id' => $id, 'char' => $char]);
}
/**
* Render an alert with a given type and message.
*/
function c_alert(string $t, string $m): string
{
return "<div class=\"alert $t\">$m</div>";
}
/**
* Generate a form field.
*/
function c_form_field(string $type, string $name, string $id, string $placeholder, bool $required = false): string
{
return render('components/form_field', ['type' => $type, 'name' => $name, 'id' => $id, 'placeholder' => $placeholder, 'required' => $required]);
}

View File

@ -56,7 +56,7 @@ function auth_controller_register_post(): void
// 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)) {
flash('errors', $errors); flash('alert-registration', ['errors', $errors]);
redirect('/auth/register'); redirect('/auth/register');
} }
@ -76,7 +76,7 @@ function auth_controller_register_post(): void
// 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)) {
flash('errors', $errors); flash('alert-registration', ['errors', $errors]);
redirect('/auth/register'); redirect('/auth/register');
} }
@ -84,6 +84,7 @@ function auth_controller_register_post(): void
if ($user === false) router_error(400); if ($user === false) router_error(400);
$_SESSION['user'] = user_find($u); $_SESSION['user'] = user_find($u);
wallet_create($_SESSION['user']['id']);
redirect('/character/create-first'); redirect('/character/create-first');
} }
@ -169,28 +170,3 @@ function auth_controller_logout_post(): void
set_cookie('remember_me', '', 1); set_cookie('remember_me', '', 1);
redirect('/'); redirect('/');
} }
/**
* Changes the user's currently selected character.
*/
function auth_controller_change_character_post(): void
{
auth_only();
must_have_character();
csrf_ensure();
$char_id = (int) ($_POST['char_id'] ?? 0);
// If the character ID is the current character, do nothing.
if ($char_id === $_SESSION['user']['char_id']) redirect('/');
// Make sure the character ID is valid.
if (char_exists($char_id) === false) throw new Exception('Invalid character ID. (acccp)');
// Make sure the user owns the character.
if (char_belongs_to_user($char_id, $_SESSION['user']['id']) === false) router_error(999);
change_user_character($char_id);
redirect('/');
}

View File

@ -3,14 +3,110 @@
/** /**
* Display a list of characters for the currently logged in user. * Display a list of characters for the currently logged in user.
*/ */
function char_controller_select_get(): void function char_controller_list_get(): void
{ {
auth_only(); auth_only();
must_have_character(); must_have_character();
$chars = char_list(user('id')); $chars = char_list(user('id'));
echo render('layouts/basic', ['view' => 'pages/chars/select', 'chars' => $chars, 'activeTab' => nav_tabs['chars']]); echo render('layouts/basic', ['view' => 'pages/chars/list', 'chars' => $chars, 'activeTab' => nav_tabs['chars']]);
}
/**
* Handle an action from the character list page.
*/
function char_controller_list_post(): void
{
auth_only();
must_have_character();
csrf_ensure();
$char_id = (int) ($_POST['char_id'] ?? 0);
$action = $_POST['action'] ?? '';
// If the character ID is not a number, or the action is not a string, return a 400.
if (!is_numeric($char_id) || !is_string($action)) router_error(400);
// If the action is not one of the allowed actions, return a 400.
if (!in_array($action, ['select', 'delete'])) router_error(400);
// If the action is to select a character, change the user's selected character.
if ($action === 'select') {
// If the character ID is the current character, do nothing.
if ($char_id === $_SESSION['user']['char_id'] || $char_id === 0) {
flash('info', 'You are already using that character.');
redirect('/characters');
}
// Make sure the character ID is valid.
if (char_exists($char_id) === false) throw new Exception('Invalid character ID. (acccp)');
// Make sure the user owns the character.
if (!char_belongs_to_user($char_id, $_SESSION['user']['id'])) router_error(999);
change_user_character($char_id);
flash('success', 'Switched to character ' . char('name') . '!');
}
// If the action is to delete a character, move to the confirmation page.
if ($action === 'delete') {
// Make sure the character ID is valid.
if (char_exists($char_id) === false) throw new Exception('Invalid character ID. (accdp)');
// Make sure the user owns the character.
if (!char_belongs_to_user($char_id, $_SESSION['user']['id'])) router_error(999);
$char = char_find($char_id);
echo render('layouts/basic', ['view' => 'pages/chars/delete', 'char' => $char, 'activeTab' => nav_tabs['chars']]);
return;
}
redirect('/characters');
}
/**
* Delete a character for the currently logged in user.
*/
function char_controller_delete_post(): void
{
auth_only();
must_have_character();
csrf_ensure();
$char_id = (int) ($_POST['char_id'] ?? 0);
$name = $_POST['name'] ?? '';
// If the character ID is not a number, return a 400.
if (!is_numeric($char_id)) router_error(400);
// Make sure the character ID is valid.
if (char_exists($char_id) === false) throw new Exception('Invalid character ID. (acddp)');
// Make sure the user owns the character.
if (!char_belongs_to_user($char_id, $_SESSION['user']['id'])) router_error(999);
$char = char_find($char_id);
// Confirm the name matches the name of the character.
if ($char['name'] !== $_POST['name']) {
flash('error', 'Failed to delete character. Name confirmation did not match.');
redirect('/characters');
}
// Delete the character
char_delete($char_id);
// If the character being deleted is the currently selected character, select the first character.
if ($_SESSION['user']['char_id'] === $char_id) {
$chars = char_list(user('id'));
if (count($chars) > 0) change_user_character($chars[0]['id']);
}
flash('error', 'Character ' . $char['name'] . ' deleted.');
redirect('/characters');
} }
/** /**
@ -57,8 +153,8 @@ function char_controller_create_post(): void
// 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)) {
flash('errors', $errors); flash('alert-cl2', ['errors', $errors]);
redirect('/'); redirect('/characters');
} }
// Create the character // Create the character
@ -67,11 +163,12 @@ function char_controller_create_post(): void
// Create the auxiliary tables // Create the auxiliary tables
char_location_create($char); char_location_create($char);
char_wallet_create($char);
char_gear_create($char); char_gear_create($char);
// Set the character as the user's selected character // Set the character as the user's selected character
change_user_character($char); change_user_character($char);
redirect('/'); flash('alert_character_list_1', ['success', 'Character ' . $name . ' created!']);
redirect('/characters');
} }

View File

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

View File

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

View File

@ -61,22 +61,6 @@ function char_location_create(int $char_id, int $x = 0, int $y = 0, int $current
} }
} }
/**
* Creates a character's wallet. A character's wallet is where they store their currencies. Can optionally specify the
* starting balances of the wallet. Returns the created wallet's ID. If a currency is set to -1, the starting_silver
* or starting_star_gems fields from the env will be used.
*/
function char_wallet_create(int $char_id, int $silver = -1, int $starGems = -1): void
{
if (db_query(db_live(), "INSERT INTO char_wallets (char_id, silver, stargem) VALUES (:p, :s, :sg)", [
':p' => $char_id,
':s' => $silver === -1 ? env('start_silver', 10) : $silver,
':sg' => $starGems === -1 ? env('start_star_gems', 0) : $starGems
]) === false) {
throw new Exception('Failed to create character wallet. (cwc)');
}
}
/** /**
* Create the character's gear table. A character's gear is where they store their equipped items. * Create the character's gear table. A character's gear is where they store their equipped items.
* @TODO: implement initial gear * @TODO: implement initial gear
@ -88,23 +72,6 @@ function char_gear_create(int $char_id, array $initialGear = []): void
} }
} }
/**
* Create the character's bank account. The bank stores items and currency, with an interest rate based on
* the bank account's tier. The bank account has a limited number of slots, which can be increased by upgrading
* the bank account. The bank account starts with 0 silver and 5 slots.
*/
function char_bank_create(int $char_id, int $slots = 5, int $silver = 0, int $tier = 0): void
{
if (db_query(db_live(), "INSERT INTO char_banks (char_id, slots, silver, tier) VALUES (:p, :s, :si, :t)", [
':p' => $char_id,
':s' => $slots,
':si' => $silver,
':t' => $tier
]) === false) {
throw new Exception('Failed to create character bank. (cbc)');
}
}
/** /**
* Get a charcter by their ID. Returns the character's data as an associative array. * Get a charcter by their ID. Returns the character's data as an associative array.
*/ */
@ -152,16 +119,6 @@ function char_get_location(int $char_id): array
return $location; return $location;
} }
/**
* Get a character's wallet by their character ID. Returns the wallet's data as an associative array.
*/
function char_get_wallet(int $char_id): array
{
$wallet = db_query(db_live(), "SELECT * FROM char_wallets WHERE char_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC);
if ($wallet === false) throw new Exception('Wallet not found. (cgw)');
return $wallet;
}
/** /**
* See if a character name exists. * See if a character name exists.
*/ */
@ -187,3 +144,89 @@ function char_belongs_to_user(int $char_id, int $user_id): bool
if ($char === false) throw new Exception('Character not found. (cbtu)'); if ($char === false) throw new Exception('Character not found. (cbtu)');
return $char['user_id'] === $user_id; return $char['user_id'] === $user_id;
} }
/**
* Delete a character by their ID. This will delete all associated data tables as well.
*/
function char_delete(int $char_id): void
{
// 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)');
}
}
}

View File

@ -35,3 +35,18 @@ function user_delete(string|int $user): SQLite3Result|false
{ {
return db_query(db_auth(), "DELETE FROM users WHERE username = :u OR email = :u OR id = :u", [':u' => $user]); return db_query(db_auth(), "DELETE FROM users WHERE username = :u OR email = :u OR id = :u", [':u' => $user]);
} }
/**
* Creates 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(int $user_id, int $silver = -1, int $starGems = -1): void
{
if (db_query(db_live(), "INSERT INTO wallets (user_id, silver, stargem) VALUES (:u, :s, :sg)", [
':u' => $user_id,
':s' => $silver === -1 ? env('start_silver', 10) : $silver,
':sg' => $starGems === -1 ? env('start_star_gems', 0) : $starGems
]) === false) {
throw new Exception('Failed to create wallet. (wc)');
}
}

View File

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

View File

@ -0,0 +1,7 @@
<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' : '' ?>>
<label for="char_<?= $id ?>">
<?= $char['name'] ?>
<span class="badge"><?= $char['level'] ?></span>
</label>
</div>

View File

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

View File

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

View File

@ -0,0 +1,24 @@
<div class="container-960">
<h1 class="my-4">Delete Character</h1>
<p class="mb-2">
<b>Warning!</b> This action is irreversible. Once you delete a character, it is gone forever. All items, banked items and currency,
fight records, and other data associated with the character will be lost.
</p>
<p class="mb-4">
Are you <b>absolutely sure</b> you want to <b>delete this character</b>? If so, type the character's name below to confirm.
Otherwise, click the back button to cancel.
</p>
<form action="/character/delete" method="post">
<?= csrf_field() ?>
<input type="hidden" name="char_id" value="<?= $char['id'] ?>">
<label for="name">Type <b><?= $char['name'] ?></b> below.</label>
<input id="name" class="form control mb-2" type="text" name="name" placeholder="Character Name">
<button class="ui button danger" type="submit">Delete</button>
<a class="ui button" href="/characters">Back</a>
</form>
</div>

View File

@ -0,0 +1,45 @@
<section>
<?php
if (($f = flash('alert_character_list_1')) !== false) echo c_alert($f[0], $f[1]);
?>
<h1>Characters <span class="badge"><?= count($chars) . '/' . user('char_slots') ?></span></h1>
<?php
if (count($chars) > 0): ?>
<form action="/characters" method="post">
<input type="hidden" name="csrf" value="<?= csrf() ?>">
<div class="my-4 character-select">
<?php foreach ($chars as $id => $char) echo c_char_select_box($id, $char); ?>
</div>
<button type="submit" class="ui button primary" name="action" value="select">Select</button>
<button type="submit" class="ui button danger" name="action" value="delete">Delete</button>
</form>
<?php else: ?>
<!-- Should never see this particular message. If you have, there's a bug. -->
<p>You have no characters.</p>
<?php endif; ?>
</section>
<section>
<?php
if (($f = flash('alert_character_list_2')) !== false) echo c_alert($f[0], $f[1]);
if (($f = flash('errors_create_character')) !== false) echo c_alert('danger', array_to_ul($f));
?>
<?php if (user('char_slots') > count($chars)): ?>
<h2>Create a new character</h2>
<?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>
<form action="/character/create" method="post">
<input type="hidden" name="csrf" value="<?= csrf() ?>">
<div class="my-4">
<input class="form control" type="text" name="name" id="name" placeholder="Character name" required>
</div>
<button type="submit" class="ui button secondary">Create</button>
</form>
<?php endif; ?>
</section>

View File

@ -1,16 +0,0 @@
<h1>Characters</h1>
<?php
$list = char_list(user('id'));
if (count($list) > 0): ?>
<form action="/character/select" method="post">
<input type="hidden" name="csrf" value="<?= csrf() ?>">
<?php foreach ($list as $id => $char): ?>
<input type="radio" name="char_id" value="<?= $id ?>" id="char_<?= $id ?>">
<label for="char_<?= $id ?>"><?= $char['name'] ?> (Level <?= $char['level'] ?>)</label><br>
<?php endforeach; ?>
<input type="submit" value="Select Character">
</form>
<?php else: ?>
<!-- Should never see this particular message. If you have, there's a bug. -->
<p>You have no characters.</p>
<?php endif; ?>