continue work on styling and form helpers
This commit is contained in:
parent
9da77307af
commit
2263134e55
BIN
database/auth.db
BIN
database/auth.db
Binary file not shown.
Binary file not shown.
BIN
database/live.db
BIN
database/live.db
Binary file not shown.
|
@ -63,6 +63,7 @@ if ($database === AUTH || $database === 'reset') {
|
||||||
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
|
||||||
)');
|
)');
|
||||||
|
@ -406,14 +407,14 @@ if ($database === LIVE || $database === 'reset') {
|
||||||
|
|
||||||
// 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
|
// Create an index for char_id
|
||||||
$db->exec('CREATE INDEX idx_inventory_char_id ON inventory (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 wallets');
|
$db->exec('DROP TABLE IF EXISTS wallets');
|
||||||
|
@ -429,7 +430,7 @@ if ($database === LIVE || $database === 'reset') {
|
||||||
|
|
||||||
// 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,
|
||||||
|
@ -438,22 +439,22 @@ if ($database === LIVE || $database === 'reset') {
|
||||||
last_collected DATETIME DEFAULT CURRENT_TIMESTAMP
|
last_collected DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
)');
|
)');
|
||||||
// Create an index for char_id
|
// Create an index for char_id
|
||||||
$db->exec('CREATE INDEX idx_bank_char_id ON bank (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
|
// Create an index for char_id
|
||||||
$db->exec('CREATE INDEX idx_banked_items_char_id ON banked_items (char_id)');
|
$db->exec('CREATE INDEX idx_banked_items_char_id ON char_banked_items (char_id)');
|
||||||
// Create an index for item_id
|
// Create an index for item_id
|
||||||
$db->exec('CREATE INDEX idx_banked_items_item_id ON banked_items (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');
|
||||||
|
|
|
@ -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; }
|
||||||
|
@ -265,3 +283,63 @@ span.badge {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-family: monospace;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
@ -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
|
||||||
|
|
|
@ -38,3 +38,27 @@ function c_debug_query_log(): string
|
||||||
{
|
{
|
||||||
return render('components/debug_query_log');
|
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]);
|
||||||
|
}
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,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('/');
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -72,5 +168,7 @@ function char_controller_create_post(): void
|
||||||
// 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -186,3 +186,13 @@ function wallet(string $field = ''): array|int|false
|
||||||
if ($field === '') return $GLOBALS['wallet'];
|
if ($field === '') return $GLOBALS['wallet'];
|
||||||
return $GLOBALS['wallet'][$field] ?? false;
|
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>";
|
||||||
|
}
|
||||||
|
|
|
@ -72,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.
|
||||||
*/
|
*/
|
||||||
|
@ -161,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)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -37,9 +37,8 @@ function user_delete(string|int $user): SQLite3Result|false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a character's wallet. A character's wallet is where they store their currencies. Can optionally specify the
|
* Creates an account wallet. Can optionally specify the starting balances of the wallet. Returns the created wallet's
|
||||||
* starting balances of the wallet. Returns the created wallet's ID. If a currency is set to -1, the starting_silver
|
* ID. If a currency is set to -1, the starting_silver or starting_star_gems fields from the env will be used.
|
||||||
* or starting_star_gems fields from the env will be used.
|
|
||||||
*/
|
*/
|
||||||
function wallet_create(int $user_id, int $silver = -1, int $starGems = -1): void
|
function wallet_create(int $user_id, int $silver = -1, int $starGems = -1): void
|
||||||
{
|
{
|
||||||
|
|
7
templates/components/char_select_box.php
Normal file
7
templates/components/char_select_box.php
Normal 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>
|
24
templates/pages/chars/delete.php
Normal file
24
templates/pages/chars/delete.php
Normal 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>
|
45
templates/pages/chars/list.php
Normal file
45
templates/pages/chars/list.php
Normal 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>
|
|
@ -1,15 +0,0 @@
|
||||||
<h1>Characters</h1>
|
|
||||||
<?php
|
|
||||||
if (count($chars) > 0): ?>
|
|
||||||
<form action="/character/select" method="post">
|
|
||||||
<input type="hidden" name="csrf" value="<?= csrf() ?>">
|
|
||||||
<?php foreach ($chars 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; ?>
|
|
Loading…
Reference in New Issue
Block a user