diff --git a/.env b/.env index 2a885ec..86c2aa0 100644 --- a/.env +++ b/.env @@ -1,4 +1,5 @@ debug = true +version = 0.1.0 open = true world_size = 250 exp_modifier = 1 diff --git a/database/auth.db b/database/auth.db index c7f1b9e..c511977 100644 Binary files a/database/auth.db and b/database/auth.db differ diff --git a/database/live.db b/database/live.db index f2c5079..ebf2ebd 100644 Binary files a/database/live.db and b/database/live.db differ diff --git a/public/assets/css/dragon.css b/public/assets/css/dragon.css new file mode 100644 index 0000000..4716166 --- /dev/null +++ b/public/assets/css/dragon.css @@ -0,0 +1,174 @@ +@import '/assets/css/forms.css'; + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; +} + +html { + font-size: 16px; +} + +body { + background-color: #bcc6cf; + background-image: url('/assets/img/bg.jpg'); + background-attachment: fixed; + background-position: center top; + background-repeat: no-repeat; +} + +.ui.button { + 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)); + color: #111111; + margin: 0rem 0.25rem 0rem 0rem; + padding: 0.5rem 1rem 0.5rem; + text-align: center; + border-radius: 3px; + box-shadow: 0 1px 0 1px rgba(255, 255, 255, 0.3) inset, 0 0 0 1px #adb2bb inset; + 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); + } + + &.primary { + background-color: #f4cc67; + background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.1)); + box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset; + color: #111111; + border: 1px solid; + border-color: #C59F43 #AA8326 #957321; + + &:hover { + background-color: #fac847; + border-color: #C59F43 #AA8326 #957321; + } + } + + &.secondary { + 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; + + &:hover { + background-color: #4e5964; + border-color: #32373E #24282D #212429; + } + } +} + +header { + height: 76px; + color: white; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1rem; + background-image: url('/assets/img/header.jpg'); + + h1 { + margin: 0; + padding: 0; + } + + .right { + display: flex; + align-items: center; + + p { + margin-right: 1rem; + } + } +} + +main { + padding: 1rem; +} + +footer { + display: flex; + justify-content: center; + align-items: center; + margin: 1rem 0; + padding: 1rem; + text-align: center; + color: #666; + + & > p:not(:last-child) { + margin-right: 2rem; + } +} + +#char-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 1rem; + height: 34px; + color: white; + background-image: url('/assets/img/deco-bar2.jpg'); + + & > div { + display: flex; + align-items: center; + + .icon { + width: 18px; + margin-right: 0.5rem; + } + } +} + +span.badge { + font-size: 10px; + background-color: #f7f8fa; + color: #111111; + border-radius: 0.25rem; + padding: 0.1rem 0.25rem; +} + +.my-1 { margin-bottom: 0.25rem; margin-top: 0.25rem; } +.my-2 { margin-bottom: 0.5rem; margin-top: 0.5rem; } +.my-3 { margin-bottom: 0.75rem; margin-top: 0.75rem; } +.my-4 { margin-bottom: 1rem; margin-top: 1rem; } + +.ml-1 { margin-left: 0.25rem; } +.ml-2 { margin-left: 0.5rem; } +.ml-3 { margin-left: 0.75rem; } +.ml-4 { margin-left: 1rem; } + +.mr-1 { margin-right: 0.25rem; } +.mr-2 { margin-right: 0.5rem; } +.mr-3 { margin-right: 0.75rem; } +.mr-4 { margin-right: 1rem; } + +.mb-1 { margin-bottom: 0.25rem; } +.mb-2 { margin-bottom: 0.5rem; } +.mb-3 { margin-bottom: 0.75rem; } +.mb-4 { margin-bottom: 1rem; } + +.mt-1 { margin-top: 0.25rem; } +.mt-2 { margin-top: 0.5rem; } +.mt-3 { margin-top: 0.75rem; } +.mt-4 { margin-top: 1rem; } + + +.container-960 { + width: 960px; + margin: 0 auto; +} diff --git a/public/assets/css/forms.css b/public/assets/css/forms.css new file mode 100644 index 0000000..c690cd0 --- /dev/null +++ b/public/assets/css/forms.css @@ -0,0 +1,23 @@ +.form.control { + appearance: none; + outline: none; + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, -webkit-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; +} diff --git a/public/assets/img/bg.jpg b/public/assets/img/bg.jpg new file mode 100644 index 0000000..73b5cc8 Binary files /dev/null and b/public/assets/img/bg.jpg differ diff --git a/public/assets/img/deco-bar2.jpg b/public/assets/img/deco-bar2.jpg new file mode 100644 index 0000000..bdaa24a Binary files /dev/null and b/public/assets/img/deco-bar2.jpg differ diff --git a/public/assets/img/header.jpg b/public/assets/img/header.jpg new file mode 100644 index 0000000..7780e50 Binary files /dev/null and b/public/assets/img/header.jpg differ diff --git a/public/assets/img/icons/user1.png b/public/assets/img/icons/user1.png new file mode 100644 index 0000000..6842e40 Binary files /dev/null and b/public/assets/img/icons/user1.png differ diff --git a/public/index.php b/public/index.php index 4c85bd9..ec034dd 100644 --- a/public/index.php +++ b/public/index.php @@ -12,6 +12,7 @@ $r = []; Home */ router_get($r, '/', function () { + if (user()) auth_char_ensure(); echo render('layouts/basic', ['view' => 'pages/home']); }); @@ -29,6 +30,7 @@ router_post($r, '/auth/logout', 'auth_controller_logout_post'); */ router_post($r, '/character/create', 'char_controller_create_post'); router_post($r, '/character/select', 'auth_controller_change_character_post'); +router_get($r, '/character/create-first', 'char_controller_create_first_get'); /* Router diff --git a/src/auth.php b/src/auth.php index 1452aef..a880a99 100644 --- a/src/auth.php +++ b/src/auth.php @@ -46,6 +46,7 @@ function auth_check(): bool $user = user_find($session['user_id']); unset($user['password']); $_SESSION['user'] = user_find($session['user_id']); + $_SESSION['char'] = char_find($user['char_id']); return true; } } @@ -69,3 +70,33 @@ function auth_guest(): void { if (auth_check()) redirect('/'); } + +/** + * Ensure the user has a character selected. If they have no character, redirect to the character creation page. Otherwise, + * select the first character attached to the user. + */ +function auth_char_ensure(): void +{ + // If there is a character selected, and the data exists, return early. + if ($_SESSION['user']['char_id'] !== 0 && !empty($_SESSION['char'])) return; + + // If there is a character selected, make sure the session is up to date. + if ($_SESSION['user']['char_id'] !== 0 && empty($_SESSION['char'])) { + $char = db_query(db_live(), 'SELECT * FROM characters WHERE id = :c', [':c' => $_SESSION['user']['char_id']]); + $char = $char = $char->fetchArray(SQLITE3_ASSOC); + $_SESSION['char'] = $char; + return; + } + + // if no characters, redirect to create first + if (char_count(user('id')) === 0) redirect('/character/create-first'); + + // if no character selected, select the first one + if ($_SESSION['user']['char_id'] === 0) { + $char = db_query(db_live(), 'SELECT * FROM characters WHERE user_id = :u ORDER BY id ASC LIMIT 1', [':u' => user('id')]); + $char = $char->fetchArray(SQLITE3_ASSOC); + $_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')]); + $_SESSION['char'] = $char; + } +} diff --git a/src/bootstrap.php b/src/bootstrap.php index b2c9d43..0dbdd06 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -10,6 +10,7 @@ require_once SRC . '/env.php'; require_once SRC . '/database.php'; require_once SRC . '/auth.php'; require_once SRC . '/router.php'; +require_once SRC . '/components.php'; // Database models require_once SRC . '/models/user.php'; diff --git a/src/components.php b/src/components.php new file mode 100644 index 0000000..8ac2afa --- /dev/null +++ b/src/components.php @@ -0,0 +1,19 @@ + $char]); +} diff --git a/src/controllers/auth.php b/src/controllers/auth.php index e36d670..3031628 100644 --- a/src/controllers/auth.php +++ b/src/controllers/auth.php @@ -5,7 +5,7 @@ */ function auth_controller_register_get(): void { - auth_guest(); + gate(false, false); echo render('layouts/basic', ['view' => 'pages/auth/register']); } @@ -14,7 +14,7 @@ function auth_controller_register_get(): void */ function auth_controller_register_post(): void { - auth_guest(); + gate(false, false); csrf_ensure(); $errors = []; @@ -84,7 +84,7 @@ function auth_controller_register_post(): void if ($user === false) router_error(400); $_SESSION['user'] = user_find($u); - redirect('/'); + redirect('/character/create-first'); } /** @@ -92,7 +92,7 @@ function auth_controller_register_post(): void */ function auth_controller_login_get(): void { - auth_guest(); + gate(false, false); echo render('layouts/basic', ['view' => 'pages/auth/login']); } @@ -101,7 +101,7 @@ function auth_controller_login_get(): void */ function auth_controller_login_post(): void { - auth_guest(); + gate(false, false); csrf_ensure(); $errors = []; @@ -140,6 +140,7 @@ function auth_controller_login_post(): void } $_SESSION['user'] = $user; + $_SESSION['char'] = char_find($user['char_id']); if ($_POST['remember'] ?? false) auth_rememberMe(); redirect('/'); } @@ -152,6 +153,7 @@ function auth_controller_logout_post(): void csrf_ensure(); session_delete($_SESSION['user']['id']); unset($_SESSION['user']); + unset($_SESSION['char']); set_cookie('remember_me', '', 1); redirect('/'); } @@ -161,7 +163,7 @@ function auth_controller_logout_post(): void */ function auth_controller_change_character_post(): void { - auth_check(); + gate(true); csrf_ensure(); $char_id = (int) ($_POST['char_id'] ?? 0); @@ -172,5 +174,8 @@ function auth_controller_change_character_post(): void ':c' => $char_id, ':u' => $_SESSION['user']['id'] ]) === false) router_error(400); + + $_SESSION['char'] = char_find($char_id); + redirect('/'); } diff --git a/src/controllers/char.php b/src/controllers/char.php index c190d99..afdcd92 100644 --- a/src/controllers/char.php +++ b/src/controllers/char.php @@ -1,11 +1,24 @@ 0) redirect('/'); + + echo render('layouts/basic', ['view' => 'pages/chars/first']); +} + /** * Create a player for the currently logged in user. */ function char_controller_create_post(): void { - auth_ensure(); + gate(false); csrf_ensure(); $errors = []; @@ -46,22 +59,3 @@ function char_controller_create_post(): void redirect('/'); } - -/** - * Change the user's selected character. - */ -function char_controller_select_post(): void -{ - auth_ensure(); - csrf_ensure(); - - $char_id = (int) $_POST['char_id'] ?? 0; - - // Ensure the character exists and belongs to the user - if (!char_exists($char_id)) router_error(400); - - // Update the user's selected character - $_SESSION['user']['char_id'] = $char_id; - - redirect('/'); -} diff --git a/src/helpers.php b/src/helpers.php index d9212fd..671b2a5 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -122,7 +122,28 @@ function user(string $field = ''): mixed /** * Check whether the user has selected a character. If so, return the character's ID. */ -function char_selected(): int +function user_selected_char(): int { return (int) $_SESSION['user']['char_id']; } + +/** + * 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. + */ +function char(string $field = ''): mixed +{ + if (empty($_SESSION['char'])) return false; + if ($field === '') return $_SESSION['char']; + return $_SESSION['char'][$field] ?? false; +} + +/** + * Perform an authentication and optionally a character check. Failing user auth will redirect to the login page. Failing + * the character check will redirect to the character creation page. + */ +function gate(bool $char = false, bool $user = true): void +{ + if ($user && !auth_check()) redirect('/auth/login'); + if ($char) auth_char_ensure(); +} diff --git a/src/models/char.php b/src/models/char.php index c89e97f..f797541 100644 --- a/src/models/char.php +++ b/src/models/char.php @@ -106,17 +106,13 @@ function char_bank_create(int $char_id, int $slots = 5, int $silver = 0, int $ti } /** - * Get a player by their account ID. Returns the player's data as an associative array. + * Get a charcter by their ID. Returns the character's data as an associative array. */ function char_find(int $char_id): array { - // Get the player - $player = db_query(db_live(), "SELECT * FROM characters WHERE id = :id", [':id' => $char_id])->fetchArray(SQLITE3_ASSOC); - if ($player === false) { - throw new Exception('Character not found.'); - } - - return $player; + $char = db_query(db_live(), "SELECT * FROM characters WHERE id = :id", [':id' => $char_id])->fetchArray(SQLITE3_ASSOC); + if ($char === false) throw new Exception('Character not found.'); + return $char; } /** @@ -124,12 +120,8 @@ function char_find(int $char_id): array */ function char_count(int $user_id): int { - // Get the count $count = db_query(db_live(), "SELECT COUNT(*) FROM characters WHERE user_id = :u", [':u' => $user_id])->fetchArray(SQLITE3_NUM); - if ($count === false) { - throw new Exception('Failed to count players.'); - } - + if ($count === false) throw new Exception('Failed to count players.'); return (int) $count[0]; } @@ -138,7 +130,6 @@ function char_count(int $user_id): int */ function char_list(int $user_id): array { - // Get the players $stmt = db_query(db_live(), "SELECT id, name, level FROM characters WHERE user_id = :u", [':u' => $user_id]); if ($stmt === false) throw new Exception('Failed to list players.'); @@ -157,10 +148,7 @@ function char_get_location(int $char_id): array { // Get the location $location = db_query(db_live(), "SELECT * FROM char_locations WHERE char_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC); - if ($location === false) { - throw new Exception('Location not found.'); - } - + if ($location === false) throw new Exception('Location not found.'); return $location; } @@ -169,12 +157,8 @@ function char_get_location(int $char_id): array */ function char_get_wallet(int $char_id): array { - // Get the wallet $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.'); - } - + if ($wallet === false) throw new Exception('Wallet not found.'); return $wallet; } @@ -183,12 +167,8 @@ function char_get_wallet(int $char_id): array */ function char_nameExists(string $name): bool { - // Check for the name $exists = db_query(db_live(), "SELECT COUNT(*) FROM characters WHERE name = :n", [':n' => $name])->fetchArray(SQLITE3_NUM); - if ($exists === false) { - throw new Exception('Failed to check for player name.'); - } - + if ($exists === false) throw new Exception('Failed to check for player name.'); return (int) $exists[0] > 0; } diff --git a/templates/components/char_bar.php b/templates/components/char_bar.php new file mode 100644 index 0000000..c1a6de9 --- /dev/null +++ b/templates/components/char_bar.php @@ -0,0 +1,6 @@ +