";
+}
+
+/**
+ * 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]);
+}
diff --git a/src/controllers/auth.php b/src/controllers/auth.php
index ff89057..e6eddc5 100644
--- a/src/controllers/auth.php
+++ b/src/controllers/auth.php
@@ -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 (!empty($errors)) {
- flash('errors', $errors);
+ flash('alert-registration', ['errors', $errors]);
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 (!empty($errors)) {
- flash('errors', $errors);
+ flash('alert-registration', ['errors', $errors]);
redirect('/auth/register');
}
@@ -170,28 +170,3 @@ function auth_controller_logout_post(): void
set_cookie('remember_me', '', 1);
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('/');
-}
diff --git a/src/controllers/char.php b/src/controllers/char.php
index 02e82f8..cf45db0 100644
--- a/src/controllers/char.php
+++ b/src/controllers/char.php
@@ -3,14 +3,110 @@
/**
* 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();
must_have_character();
$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 (!empty($errors)) {
- flash('errors', $errors);
- redirect('/');
+ flash('alert-cl2', ['errors', $errors]);
+ redirect('/characters');
}
// Create the character
@@ -72,5 +168,7 @@ function char_controller_create_post(): void
// Set the character as the user's selected character
change_user_character($char);
- redirect('/');
+ flash('alert_character_list_1', ['success', 'Character ' . $name . ' created!']);
+ redirect('/characters');
}
+
diff --git a/src/helpers.php b/src/helpers.php
index ebc4dd3..6582e16 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -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
{
@@ -186,3 +186,13 @@ function wallet(string $field = ''): array|int|false
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 .= "
$item
";
+ return "
$html
";
+}
diff --git a/src/models/char.php b/src/models/char.php
index a10da4c..d78efb4 100644
--- a/src/models/char.php
+++ b/src/models/char.php
@@ -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.
*/
@@ -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)');
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)');
+ }
+ }
+}
diff --git a/src/models/user.php b/src/models/user.php
index 80a7506..c2a475e 100644
--- a/src/models/user.php
+++ b/src/models/user.php
@@ -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
- * 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.
+ * 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
{
diff --git a/templates/components/char_select_box.php b/templates/components/char_select_box.php
new file mode 100644
index 0000000..4781f90
--- /dev/null
+++ b/templates/components/char_select_box.php
@@ -0,0 +1,7 @@
+
+ >
+
+
diff --git a/templates/pages/chars/delete.php b/templates/pages/chars/delete.php
new file mode 100644
index 0000000..a119b1c
--- /dev/null
+++ b/templates/pages/chars/delete.php
@@ -0,0 +1,24 @@
+
+
Delete Character
+
+
+ Warning! 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.
+
+
+
+ Are you absolutely sure you want to delete this character? If so, type the character's name below to confirm.
+ Otherwise, click the back button to cancel.
+