functions.php

This commit is contained in:
Valithor Obsidion 2025-08-14 15:47:25 -04:00
parent feba005335
commit 4d5c345528
27 changed files with 1232 additions and 1191 deletions

View File

@ -13,6 +13,10 @@
"team-reflex/discord-php": "^10.18.45" "team-reflex/discord-php": "^10.18.45"
}, },
"autoload": { "autoload": {
"files": [
"src/DragonKnight/functions.php",
"src/DragonKnight/DragonKnight.php"
],
"psr-4": { "psr-4": {
"DragonKnight\\": "src/DragonKnight" "DragonKnight\\": "src/DragonKnight"
} }

View File

@ -21,20 +21,20 @@ use DragonKnight\Admin\Admin;
// Do an early return with babblebox data if that's what's being requested // Do an early return with babblebox data if that's what's being requested
if ($uri[0] === 'babblebox' && (isset($uri[1]) && $uri[1] === 'messages')) { if ($uri[0] === 'babblebox' && (isset($uri[1]) && $uri[1] === 'messages')) {
echo Lib::babblebox_messages(); echo babblebox_messages();
exit; exit;
} }
$r = new Router; $r = new Router;
$r->get('/', 'DragonKnight\Lib::index'); $r->get('/', 'DragonKnight\index');
$r->post('/move', 'DragonKnight\Actions\Explore::move'); $r->post('/move', 'DragonKnight\Actions\Explore::move');
$r->get('/spell/:id', 'DragonKnight\Actions\Heal::healspells'); $r->get('/spell/:id', 'DragonKnight\Actions\Heal::healspells');
$r->get('/character', 'DragonKnight\Lib::show_character_info'); $r->get('/character', 'DragonKnight\show_character_info');
$r->get('/character/:id', 'DragonKnight\Lib::show_character_info'); $r->get('/character/:id', 'DragonKnight\show_character_info');
$r->get('/showmap', 'DragonKnight\Lib::show_map'); $r->get('/showmap', 'DragonKnight\show_map');
$r->form('/babblebox', 'DragonKnight\Lib::babblebox'); $r->form('/babblebox', 'DragonKnight\babblebox');
$r->get('/babblebox/messages', 'DragonKnight\Lib::babblebox_messages'); $r->get('/babblebox/messages', 'DragonKnight\babblebox_messages');
Towns::register_routes($r); Towns::register_routes($r);
Fight::register_routes($r); Fight::register_routes($r);
@ -57,4 +57,4 @@ $l = $r->lookup($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
if (is_int($l)) { if (is_int($l)) {
exit("Error: $l"); exit("Error: $l");
} }
echo Lib::render_response($uri, $l['handler'](...$l['params'] ?? [])); echo render_response($uri, $l['handler'](...$l['params'] ?? []));

View File

@ -13,15 +13,29 @@ declare(strict_types=1);
namespace DragonKnight\Admin; namespace DragonKnight\Admin;
use DragonKnight\Lib;
use DragonKnight\Router; use DragonKnight\Router;
use SQLite3Result; use SQLite3Result;
use function DragonKnight\db;
use function DragonKnight\env;
use function DragonKnight\get_drop;
use function DragonKnight\get_item;
use function DragonKnight\get_monster;
use function DragonKnight\get_spell;
use function DragonKnight\get_town_by_id;
use function DragonKnight\is_post;
use function DragonKnight\make_safe;
use function DragonKnight\page_title;
use function DragonKnight\render;
use function DragonKnight\ul_from_validate_errors;
use function DragonKnight\user;
use function DragonKnight\validate;
class Admin class Admin
{ {
public static function register_routes(Router $r): Router public static function register_routes(Router $r): Router
{ {
if (Lib::user() !== false && Lib::user()->authlevel === 1) { if (user() !== false && user()->authlevel === 1) {
$r->get('/admin', 'Admin\donothing'); $r->get('/admin', 'Admin\donothing');
$r->form('/admin/main', 'Admin\primary'); $r->form('/admin/main', 'Admin\primary');
@ -58,7 +72,7 @@ class Admin
*/ */
public static function donothing(): string public static function donothing(): string
{ {
Lib::page_title('Admin'); page_title('Admin');
return <<<HTML return <<<HTML
Welcome to the administration section. Use the links on the left bar to control and edit various Welcome to the administration section. Use the links on the left bar to control and edit various
@ -81,8 +95,8 @@ class Admin
*/ */
public static function primary(): string public static function primary(): string
{ {
if (Lib::is_post()) { if (is_post()) {
$form = Lib::validate($_POST, [ $form = validate($_POST, [
'gamename' => ['alphanum-spaces'], 'gamename' => ['alphanum-spaces'],
'gamesize' => ['int', 'min:5'], 'gamesize' => ['int', 'min:5'],
'class1name' => ['alpha-spaces'], 'class1name' => ['alpha-spaces'],
@ -106,7 +120,7 @@ class Admin
$page = 'Main settings updated.'; $page = 'Main settings updated.';
} else { } else {
$error_list = Lib::ul_from_validate_errors($form['errors']); $error_list = ul_from_validate_errors($form['errors']);
$page = <<<HTML $page = <<<HTML
<b>Errors:</b><br> <b>Errors:</b><br>
<div style="color: red;">{$error_list}</div><br> <div style="color: red;">{$error_list}</div><br>
@ -114,10 +128,10 @@ class Admin
HTML; HTML;
} }
} else { } else {
$page = Lib::render('admin/main_settings'); $page = render('admin/main_settings');
} }
Lib::page_title('Admin: Main Settings'); page_title('Admin: Main Settings');
return $page; return $page;
} }
@ -127,11 +141,11 @@ class Admin
*/ */
public static function items(): string public static function items(): string
{ {
$items = Lib::db()->query('SELECT * FROM items ORDER BY id;'); $items = db()->query('SELECT * FROM items ORDER BY id;');
$page = "<h2>Edit Items</h2>Click an item's name or ID to edit it.<br><br>\n"; $page = "<h2>Edit Items</h2>Click an item's name or ID to edit it.<br><br>\n";
$page .= self::build_bulk_table($items, 'name', '/admin/items'); $page .= self::build_bulk_table($items, 'name', '/admin/items');
Lib::page_title('Admin: Items'); page_title('Admin: Items');
return $page; return $page;
} }
@ -141,19 +155,19 @@ class Admin
*/ */
public static function edit_item(int $id): string public static function edit_item(int $id): string
{ {
$item = Lib::get_item($id); $item = get_item($id);
$page = Lib::is_post() $page = is_post()
? self::handle_edit_form($id, 'items', Lib::validate($_POST, [ ? self::handle_edit_form($id, 'items', validate($_POST, [
'name' => [], 'name' => [],
'type' => ['int', 'in:1,2,3'], 'type' => ['int', 'in:1,2,3'],
'buycost' => ['int', 'min:0'], 'buycost' => ['int', 'min:0'],
'attribute' => ['int', 'min:0'], 'attribute' => ['int', 'min:0'],
'special' => ['default:X'], 'special' => ['default:X'],
])) ]))
: Lib::render('admin/edit_item', ['item' => $item]); : render('admin/edit_item', ['item' => $item]);
Lib::page_title('Admin: Editing '.$item['name']); page_title('Admin: Editing '.$item['name']);
return $page; return $page;
} }
@ -163,11 +177,11 @@ class Admin
*/ */
public static function drops() public static function drops()
{ {
$drops = Lib::db()->query('SELECT * FROM drops ORDER BY id;'); $drops = db()->query('SELECT * FROM drops ORDER BY id;');
$page = "<h2>Edit Drops</h2>Click an item's name to edit it.<br><br>\n"; $page = "<h2>Edit Drops</h2>Click an item's name to edit it.<br><br>\n";
$page .= self::build_bulk_table($drops, 'name', '/admin/drops'); $page .= self::build_bulk_table($drops, 'name', '/admin/drops');
Lib::page_title('Admin: Drops'); page_title('Admin: Drops');
return $page; return $page;
} }
@ -177,20 +191,20 @@ class Admin
*/ */
public static function edit_drop(int $id): string public static function edit_drop(int $id): string
{ {
$drop = Lib::get_drop($id); $drop = get_drop($id);
if (Lib::is_post()) { if (is_post()) {
$page = self::handle_edit_form($id, 'drops', Lib::validate($_POST, [ $page = self::handle_edit_form($id, 'drops', validate($_POST, [
'name' => [], 'name' => [],
'mlevel' => ['int', 'min:1'], 'mlevel' => ['int', 'min:1'],
'attribute1' => [], 'attribute1' => [],
'attribute2' => ['default:X'], 'attribute2' => ['default:X'],
])); ]));
} else { } else {
$page = Lib::render('admin/edit_drop', ['drop' => $drop]); $page = render('admin/edit_drop', ['drop' => $drop]);
} }
Lib::page_title('Admin: Editing '.$drop['name']); page_title('Admin: Editing '.$drop['name']);
return $page; return $page;
} }
@ -200,11 +214,11 @@ class Admin
*/ */
public static function towns(): string public static function towns(): string
{ {
$towns = Lib::db()->query('SELECT * FROM towns ORDER BY id;'); $towns = db()->query('SELECT * FROM towns ORDER BY id;');
$page = "<h2>Edit Towns</h2>Click an town's name or ID to edit it.<br><br>\n"; $page = "<h2>Edit Towns</h2>Click an town's name or ID to edit it.<br><br>\n";
$page .= self::build_bulk_table($towns, 'name', '/admin/towns'); $page .= self::build_bulk_table($towns, 'name', '/admin/towns');
Lib::page_title('Admin: Towns'); page_title('Admin: Towns');
return $page; return $page;
} }
@ -214,23 +228,23 @@ class Admin
*/ */
public static function edit_town(int $id): string public static function edit_town(int $id): string
{ {
$town = Lib::get_town_by_id($id); $town = get_town_by_id($id);
if (Lib::is_post()) { if (is_post()) {
$page = self::handle_edit_form($id, 'towns', Lib::validate($_POST, [ $page = self::handle_edit_form($id, 'towns', validate($_POST, [
'name' => [], 'name' => [],
'latitude' => ['int', 'min:0', 'max:'.Lib::env('game_size')], 'latitude' => ['int', 'min:0', 'max:'.env('game_size')],
'longitude' => ['int', 'min:0', 'max:'.Lib::env('game_size')], 'longitude' => ['int', 'min:0', 'max:'.env('game_size')],
'innprice' => ['int', 'min:0'], 'innprice' => ['int', 'min:0'],
'mapprice' => ['int', 'min:0'], 'mapprice' => ['int', 'min:0'],
'travelpoints' => ['int', 'min:0'], 'travelpoints' => ['int', 'min:0'],
'itemslist' => ['optional'], 'itemslist' => ['optional'],
])); ]));
} else { } else {
$page = Lib::render('admin/edit_town', ['town' => $town]); $page = render('admin/edit_town', ['town' => $town]);
} }
Lib::page_title('Admin: Editing '.$town['name']); page_title('Admin: Editing '.$town['name']);
return $page; return $page;
} }
@ -240,19 +254,19 @@ class Admin
*/ */
public static function monsters() public static function monsters()
{ {
$max_level = Lib::db()->query('SELECT level FROM monsters ORDER BY level DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC)['level']; $max_level = db()->query('SELECT level FROM monsters ORDER BY level DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC)['level'];
$monsters = Lib::db()->query('SELECT * FROM monsters ORDER BY id;'); $monsters = db()->query('SELECT * FROM monsters ORDER BY id;');
$page = '<h2>Edit Monsters</h2>'; $page = '<h2>Edit Monsters</h2>';
$page .= ((Lib::env('game_size') / 5) !== $max_level) $page .= ((env('game_size') / 5) !== $max_level)
? '<span class="highlight">Note:</span> Your highest monster level does not match with your entered map size. Highest monster level should be '.(Lib::env('game_size') / 5).", yours is $max_level. Please fix this before opening the game to the public.<br>" ? '<span class="highlight">Note:</span> Your highest monster level does not match with your entered map size. Highest monster level should be '.(env('game_size') / 5).", yours is $max_level. Please fix this before opening the game to the public.<br>"
: 'Monster level and map size match. No further actions are required for map compatibility.<br>'; : 'Monster level and map size match. No further actions are required for map compatibility.<br>';
$page .= "Click an monster's name or ID to edit it.<br><br>\n"; $page .= "Click an monster's name or ID to edit it.<br><br>\n";
$page .= self::build_bulk_table($monsters, 'name', '/admin/monsters'); $page .= self::build_bulk_table($monsters, 'name', '/admin/monsters');
Lib::page_title('Admin: Monsters'); page_title('Admin: Monsters');
return $page; return $page;
} }
@ -262,10 +276,10 @@ class Admin
*/ */
public static function edit_monster(int $id): string public static function edit_monster(int $id): string
{ {
$monster = Lib::get_monster($id); $monster = get_monster($id);
$page = (Lib::is_post()) $page = (is_post())
? self::handle_edit_form($id, 'monsters', Lib::validate($_POST, [ ? self::handle_edit_form($id, 'monsters', validate($_POST, [
'name' => [], 'name' => [],
'maxhp' => ['int', 'min:1'], 'maxhp' => ['int', 'min:1'],
'maxdam' => ['int', 'min:0'], 'maxdam' => ['int', 'min:0'],
@ -275,9 +289,9 @@ class Admin
'maxgold' => ['int', 'min:0'], 'maxgold' => ['int', 'min:0'],
'immune' => ['in:0,1,2'], 'immune' => ['in:0,1,2'],
])) ]))
: Lib::render('admin/edit_monster', ['monster' => $monster]); : render('admin/edit_monster', ['monster' => $monster]);
Lib::page_title('Admin: Editing '.$monster['name']); page_title('Admin: Editing '.$monster['name']);
return $page; return $page;
} }
@ -289,10 +303,10 @@ class Admin
{ {
$page = "<h2>Edit Spells</h2>Click an spell's name to edit it.<br><br>\n"; $page = "<h2>Edit Spells</h2>Click an spell's name to edit it.<br><br>\n";
$spells = Lib::db()->query('SELECT * FROM spells ORDER BY id;'); $spells = db()->query('SELECT * FROM spells ORDER BY id;');
$page .= self::build_bulk_table($spells, 'name', '/admin/spells'); $page .= self::build_bulk_table($spells, 'name', '/admin/spells');
Lib::page_title('Admin: Spells'); page_title('Admin: Spells');
return $page; return $page;
} }
@ -302,18 +316,18 @@ class Admin
*/ */
public static function edit_spell(int $id): string public static function edit_spell(int $id): string
{ {
$spell = Lib::get_spell($id); $spell = get_spell($id);
$page = (Lib::is_post()) $page = (is_post())
? self::handle_edit_form($id, 'spells', Lib::validate($_POST, [ ? self::handle_edit_form($id, 'spells', validate($_POST, [
'name' => [], 'name' => [],
'mp' => ['int', 'min:0'], 'mp' => ['int', 'min:0'],
'attribute' => ['int', 'min:0'], 'attribute' => ['int', 'min:0'],
'type' => ['in:1,2,3,4,5'], 'type' => ['in:1,2,3,4,5'],
])) ]))
: Lib::render('admin/edit_spell', ['spell' => $spell]); : render('admin/edit_spell', ['spell' => $spell]);
Lib::page_title('Admin: Editing '.$spell['name']); page_title('Admin: Editing '.$spell['name']);
return $page; return $page;
} }
@ -323,7 +337,7 @@ class Admin
*/ */
public static function levels(): string public static function levels(): string
{ {
$max_level = Lib::db()->query('SELECT id FROM levels ORDER BY id DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC)['id']; $max_level = db()->query('SELECT id FROM levels ORDER BY id DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC)['id'];
$page = <<<HTML $page = <<<HTML
<h2>Edit Levels</h2> <h2>Edit Levels</h2>
@ -340,7 +354,7 @@ class Admin
</form> </form>
HTML; HTML;
Lib::page_title('Admin: Levels'); page_title('Admin: Levels');
return $page; return $page;
} }
@ -354,13 +368,13 @@ class Admin
return 'No level to edit.'; return 'No level to edit.';
} }
$id = $_POST['level']; $id = $_POST['level'];
$level = Lib::db()->query('SELECT * FROM levels WHERE id=? LIMIT 1;', [$id])->fetchArray(SQLITE3_ASSOC); $level = db()->query('SELECT * FROM levels WHERE id=? LIMIT 1;', [$id])->fetchArray(SQLITE3_ASSOC);
if (Lib::is_post() && isset($_POST['save'])) { if (is_post() && isset($_POST['save'])) {
unset($_POST['save']); unset($_POST['save']);
unset($_POST['level']); unset($_POST['level']);
$page = self::handle_edit_form($id, 'levels', Lib::validate($_POST, [ $page = self::handle_edit_form($id, 'levels', validate($_POST, [
'1_exp' => ['int', 'min:0'], '1_exp' => ['int', 'min:0'],
'1_hp' => ['int', 'min:0'], '1_hp' => ['int', 'min:0'],
'1_mp' => ['int', 'min:0'], '1_mp' => ['int', 'min:0'],
@ -384,21 +398,21 @@ class Admin
'3_spells' => ['int', 'min:0'], '3_spells' => ['int', 'min:0'],
]), 'Level <b>'.$id.'</b> updated.'); ]), 'Level <b>'.$id.'</b> updated.');
} else { } else {
$page = Lib::render('admin/edit_level', ['level' => $level]); $page = render('admin/edit_level', ['level' => $level]);
} }
Lib::page_title('Admin: Editing Level '.$id); page_title('Admin: Editing Level '.$id);
return $page; return $page;
} }
public static function users() public static function users()
{ {
$users = Lib::db()->query('SELECT * FROM users ORDER BY id;'); $users = db()->query('SELECT * FROM users ORDER BY id;');
$page = '<h2>Edit Users</h2>Click a username or ID to edit the account.<br><br><div class="table-wrapper">'; $page = '<h2>Edit Users</h2>Click a username or ID to edit the account.<br><br><div class="table-wrapper">';
$page .= self::build_bulk_table($users, 'username', '/admin/users'); $page .= self::build_bulk_table($users, 'username', '/admin/users');
Lib::page_title('Admin: Users'); page_title('Admin: Users');
return $page.'</div>'; return $page.'</div>';
} }
@ -408,17 +422,17 @@ class Admin
*/ */
public static function edit_user(int $id): string public static function edit_user(int $id): string
{ {
$user = Lib::db()->query('SELECT * FROM users WHERE id = ? LIMIT 1;', [$id])->fetchArray(SQLITE3_ASSOC); $user = db()->query('SELECT * FROM users WHERE id = ? LIMIT 1;', [$id])->fetchArray(SQLITE3_ASSOC);
if (Lib::is_post()) { if (is_post()) {
$form = Lib::validate($_POST, [ $form = validate($_POST, [
'username' => ['length:3-18', 'alpha-spaces', 'unique:users,username'], 'username' => ['length:3-18', 'alpha-spaces', 'unique:users,username'],
'verify' => [], 'verify' => [],
'authlevel' => ['int'], 'authlevel' => ['int'],
'email' => ['email', 'unique:users,email'], 'email' => ['email', 'unique:users,email'],
'charclass' => ['in:1,2,3'], 'charclass' => ['in:1,2,3'],
'latitude' => ['int', 'min:0', 'max:'.Lib::env('game_size')], 'latitude' => ['int', 'min:0', 'max:'.env('game_size')],
'longitude' => ['int', 'min:0', 'max:'.Lib::env('game_size')], 'longitude' => ['int', 'min:0', 'max:'.env('game_size')],
'currentaction' => [], 'currentaction' => [],
'currentfight' => ['int'], 'currentfight' => ['int'],
'currentmonster' => ['int'], 'currentmonster' => ['int'],
@ -463,7 +477,7 @@ class Admin
self::save_data_row('users', $form['data'], $id); self::save_data_row('users', $form['data'], $id);
$page = 'User <b>'.$user['username'].'</b> updated.'; $page = 'User <b>'.$user['username'].'</b> updated.';
} else { } else {
$error_list = Lib::ul_from_validate_errors($form['errors']); $error_list = ul_from_validate_errors($form['errors']);
$page = <<<HTML $page = <<<HTML
<b>Errors:</b><br> <b>Errors:</b><br>
<div style="color: red;">{$error_list}</div><br> <div style="color: red;">{$error_list}</div><br>
@ -471,10 +485,10 @@ class Admin
HTML; HTML;
} }
} else { } else {
$page = Lib::render('admin/edit_user', ['user' => $user]); $page = render('admin/edit_user', ['user' => $user]);
} }
Lib::page_title('Admin: Editing '.$user['username']); page_title('Admin: Editing '.$user['username']);
return $page; return $page;
} }
@ -484,7 +498,7 @@ class Admin
*/ */
public static function add_news() public static function add_news()
{ {
if (Lib::is_post()) { if (is_post()) {
$c = trim($_POST['content'] ?? ''); $c = trim($_POST['content'] ?? '');
$errors = []; $errors = [];
@ -493,7 +507,7 @@ class Admin
} }
if (count($errors) === 0) { if (count($errors) === 0) {
Lib::db()->query('INSERT INTO news (author, content) VALUES (?, ?);', [Lib::user()->username, $c]); db()->query('INSERT INTO news (author, content) VALUES (?, ?);', [user()->username, $c]);
$page = 'News post added.'; $page = 'News post added.';
} else { } else {
$error_list = implode('<br>', $errors); $error_list = implode('<br>', $errors);
@ -511,7 +525,7 @@ class Admin
HTML; HTML;
} }
Lib::page_title('Admin: Add News'); page_title('Admin: Add News');
return $page; return $page;
} }
@ -541,7 +555,7 @@ class Admin
foreach ($columns as $column) { foreach ($columns as $column) {
$html_parts[] = '<th>'. $html_parts[] = '<th>'.
Lib::make_safe($column === 'id' ? 'ID' : ucfirst($column)). make_safe($column === 'id' ? 'ID' : ucfirst($column)).
'</th>'; '</th>';
} }
$html_parts[] = '</tr></thead><tbody>'; $html_parts[] = '</tr></thead><tbody>';
@ -551,7 +565,7 @@ class Admin
foreach ($data as $row) { foreach ($data as $row) {
$html_parts[] = '<tr>'; $html_parts[] = '<tr>';
foreach ($columns as $column) { foreach ($columns as $column) {
$name = Lib::make_safe($row[$column]); $name = make_safe($row[$column]);
$html_parts[] = isset($is_edit_column[$column]) $html_parts[] = isset($is_edit_column[$column])
? "<td><a href=\"{$edit_link}/{$row['id']}\" hx-get=\"{$edit_link}/{$row['id']}\" hx-target=\"#main\">{$name}</a></td>" ? "<td><a href=\"{$edit_link}/{$row['id']}\" hx-get=\"{$edit_link}/{$row['id']}\" hx-target=\"#main\">{$name}</a></td>"
: "<td>{$name}</td>"; : "<td>{$name}</td>";
@ -578,7 +592,7 @@ class Admin
$values = array_values($data); $values = array_values($data);
$values[] = $id; $values[] = $id;
return Lib::db()->query("UPDATE $table SET $fields WHERE id=?", $values); return db()->query("UPDATE $table SET $fields WHERE id=?", $values);
} }
/** /**
@ -590,7 +604,7 @@ class Admin
self::save_data_row($table, $form['data'], $id); self::save_data_row($table, $form['data'], $id);
$page = $updated_message ?: '<b>'.$form['data']['name'].'</b> updated.'; $page = $updated_message ?: '<b>'.$form['data']['name'].'</b> updated.';
} else { } else {
$error_list = Lib::ul_from_validate_errors($form['errors']); $error_list = ul_from_validate_errors($form['errors']);
$page = <<<HTML $page = <<<HTML
<b>Errors:</b><br> <b>Errors:</b><br>
<div style="color: red;">{$error_list}</div><br> <div style="color: red;">{$error_list}</div><br>

View File

@ -13,7 +13,14 @@ declare(strict_types=1);
namespace DragonKnight\Actions; namespace DragonKnight\Actions;
use DragonKnight\Lib; use function DragonKnight\env;
use function DragonKnight\get_town_by_xy;
use function DragonKnight\index;
use function DragonKnight\page_title;
use function DragonKnight\redirect;
use function DragonKnight\ul_from_validate_errors;
use function DragonKnight\user;
use function DragonKnight\validate;
class Explore class Explore
{ {
@ -23,7 +30,7 @@ class Explore
*/ */
public static function explore() public static function explore()
{ {
Lib::page_title('Exploring'); page_title('Exploring');
return <<<HTML return <<<HTML
<div class="title"><img src="/img/title_exploring.gif" alt="Exploring"></div> <div class="title"><img src="/img/title_exploring.gif" alt="Exploring"></div>
@ -34,20 +41,20 @@ class Explore
public static function move() public static function move()
{ {
// Early exit if fighting // Early exit if fighting
if (Lib::user()->currentaction == 'Fighting') { if (user()->currentaction == 'Fighting') {
Lib::redirect('/fight'); redirect('/fight');
} }
// Validate direction // Validate direction
$form = Lib::validate($_POST, ['direction' => ['in:north,west,east,south']]); $form = validate($_POST, ['direction' => ['in:north,west,east,south']]);
if (! $form['valid']) { if (! $form['valid']) {
return Lib::ul_from_validate_errors($form['errors']); return ul_from_validate_errors($form['errors']);
} }
// Current game state // Current game state
$game_size = Lib::env('game_size'); $game_size = env('game_size');
$latitude = Lib::user()->latitude; $latitude = user()->latitude;
$longitude = Lib::user()->longitude; $longitude = user()->longitude;
$direction = $form['data']['direction']; $direction = $form['data']['direction'];
// Calculate new coordinates with boundary checks // Calculate new coordinates with boundary checks
@ -67,23 +74,23 @@ class Explore
} }
// Check for town // Check for town
$town = Lib::get_town_by_xy($longitude, $latitude); $town = get_town_by_xy($longitude, $latitude);
if ($town !== false) { if ($town !== false) {
return Towns::travelto($town['id'], false); return Towns::travelto($town['id'], false);
} }
// Determine action (1 in 5 chance of fighting) // Determine action (1 in 5 chance of fighting)
if (rand(1, 5) === 1) { if (rand(1, 5) === 1) {
Lib::user()->currentaction = 'Fighting'; user()->currentaction = 'Fighting';
Lib::user()->currentfight = 1; user()->currentfight = 1;
} else { } else {
Lib::user()->currentaction = 'Exploring'; user()->currentaction = 'Exploring';
} }
Lib::user()->latitude = $latitude; user()->latitude = $latitude;
Lib::user()->longitude = $longitude; user()->longitude = $longitude;
Lib::user()->save(); user()->save();
return Lib::index(); return index();
} }
} }

View File

@ -13,7 +13,6 @@ declare(strict_types=1);
namespace DragonKnight\Actions; namespace DragonKnight\Actions;
use DragonKnight\Lib;
use DragonKnight\Router; use DragonKnight\Router;
class Fight class Fight
@ -33,7 +32,7 @@ class Fight
*/ */
public static function fight() public static function fight()
{ {
if (Lib::user()->currentaction !== 'Fighting') { if (user()->currentaction !== 'Fighting') {
exit('Cheat attempt detected.<br><br>Get a life, loser.'); exit('Cheat attempt detected.<br><br>Get a life, loser.');
} }
@ -41,7 +40,7 @@ class Fight
$playerisdead = 0; $playerisdead = 0;
// Generate spell list // Generate spell list
$user_spells = Lib::user()->spells(); $user_spells = user()->spells();
if (! empty($user_spells)) { if (! empty($user_spells)) {
$page['magiclist'] = '<select name="userspell">'; $page['magiclist'] = '<select name="userspell">';
foreach ($user_spells as $spell) { foreach ($user_spells as $spell) {
@ -51,52 +50,52 @@ class Fight
} }
// Determine initial combat parameters // Determine initial combat parameters
$chancetoswingfirst = rand(1, 10) + (int) ceil(sqrt(Lib::user()->dexterity)); $chancetoswingfirst = rand(1, 10) + (int) ceil(sqrt(user()->dexterity));
if (Lib::user()->currentfight === 1) { if (user()->currentfight === 1) {
$maxlevel = (int) floor(max(abs(Lib::user()->latitude) + 5, abs(Lib::user()->longitude) + 5) / 5); $maxlevel = (int) floor(max(abs(user()->latitude) + 5, abs(user()->longitude) + 5) / 5);
$minlevel = max(1, $maxlevel - 2); $minlevel = max(1, $maxlevel - 2);
$monster = Lib::db()->query('SELECT * FROM monsters WHERE level >= ? AND level <= ? ORDER BY RANDOM() LIMIT 1;', [ $monster = db()->query('SELECT * FROM monsters WHERE level >= ? AND level <= ? ORDER BY RANDOM() LIMIT 1;', [
$minlevel, $maxlevel, $minlevel, $maxlevel,
])->fetchArray(SQLITE3_ASSOC); ])->fetchArray(SQLITE3_ASSOC);
Lib::user()->currentmonster = $monster['id']; user()->currentmonster = $monster['id'];
Lib::user()->currentmonsterhp = rand((int) (($monster['maxhp'] / 5) * 4), $monster['maxhp']); user()->currentmonsterhp = rand((int) (($monster['maxhp'] / 5) * 4), $monster['maxhp']);
Lib::user()->currentmonstersleep = 0; user()->currentmonstersleep = 0;
Lib::user()->currentmonsterimmune = $monster['immune']; user()->currentmonsterimmune = $monster['immune'];
$chancetoswingfirst = ($chancetoswingfirst > (rand(1, 7) + (int) ceil(sqrt($monster['maxdam'])))) ? 1 : 0; $chancetoswingfirst = ($chancetoswingfirst > (rand(1, 7) + (int) ceil(sqrt($monster['maxdam'])))) ? 1 : 0;
} }
// Get monster statistics // Get monster statistics
$monster = Lib::get_monster(Lib::user()->currentmonster); $monster = get_monster(user()->currentmonster);
$page['monstername'] = $monster['name']; $page['monstername'] = $monster['name'];
// Run action // Run action
if (isset($_POST['run'])) { if (isset($_POST['run'])) {
$chancetorun = rand(4, 10) + (int) ceil(sqrt(Lib::user()->dexterity)); $chancetorun = rand(4, 10) + (int) ceil(sqrt(user()->dexterity));
if ($chancetorun <= (rand(1, 5) + (int) ceil(sqrt($monster['maxdam'])))) { if ($chancetorun <= (rand(1, 5) + (int) ceil(sqrt($monster['maxdam'])))) {
$page['yourturn'] = 'You tried to run away, but were blocked in front!<br><br>'; $page['yourturn'] = 'You tried to run away, but were blocked in front!<br><br>';
$page['monsterhp'] = "Monster's HP: ".Lib::user()->currentmonsterhp.'<br><br>'; $page['monsterhp'] = "Monster's HP: ".user()->currentmonsterhp.'<br><br>';
// Monster turn logic (similar to original public static function) // Monster turn logic (similar to original public static function)
$page['monsterturn'] = self::handleMonsterTurn($userrow, $monster); $page['monsterturn'] = self::handleMonsterTurn($userrow, $monster);
Lib::user()->currentaction = 'Exploring'; user()->currentaction = 'Exploring';
Lib::user()->save(); user()->save();
Lib::redirect('/'); redirect('/');
} }
} }
// Fight action // Fight action
if (isset($_POST['fight'])) { if (isset($_POST['fight'])) {
// Player's attack // Player's attack
$min = (int) (Lib::user()->attackpower * 0.75); $min = (int) (user()->attackpower * 0.75);
$max = (int) (Lib::user()->attackpower / 3); $max = (int) (user()->attackpower / 3);
$tohit = (int) ceil(mt_rand(min($min, $max), max($min, $max))); $tohit = (int) ceil(mt_rand(min($min, $max), max($min, $max)));
$toexcellent = rand(1, 150); $toexcellent = rand(1, 150);
if ($toexcellent <= sqrt(Lib::user()->strength)) { if ($toexcellent <= sqrt(user()->strength)) {
$tohit *= 2; $tohit *= 2;
$page['yourturn'] .= 'Excellent hit!<br>'; $page['yourturn'] .= 'Excellent hit!<br>';
} }
@ -113,19 +112,19 @@ class Fight
$page['yourturn'] .= 'The monster is dodging. No damage has been scored.<br>'; $page['yourturn'] .= 'The monster is dodging. No damage has been scored.<br>';
} }
if (Lib::user()->currentuberdamage != 0) { if (user()->currentuberdamage != 0) {
$monsterdamage += (int) ceil($monsterdamage * (Lib::user()->currentuberdamage / 100)); $monsterdamage += (int) ceil($monsterdamage * (user()->currentuberdamage / 100));
} }
Lib::user()->currentmonsterhp -= $monsterdamage; user()->currentmonsterhp -= $monsterdamage;
$page['yourturn'] .= "You attack the monster for $monsterdamage damage.<br><br>"; $page['yourturn'] .= "You attack the monster for $monsterdamage damage.<br><br>";
$page['monsterhp'] = "Monster's HP: ".Lib::user()->currentmonsterhp.'<br><br>'; $page['monsterhp'] = "Monster's HP: ".user()->currentmonsterhp.'<br><br>';
// Check for monster defeat // Check for monster defeat
if (Lib::user()->currentmonsterhp <= 0) { if (user()->currentmonsterhp <= 0) {
Lib::user()->currentmonsterhp = 0; user()->currentmonsterhp = 0;
Lib::user()->save(); user()->save();
Lib::redirect('/victory'); redirect('/victory');
} }
// Monster's turn // Monster's turn
@ -139,26 +138,26 @@ class Fight
return 'You must select a spell first. Please go back and try again.'; return 'You must select a spell first. Please go back and try again.';
} }
$newspellrow = Lib::get_spell($pickedspell); $newspellrow = get_spell($pickedspell);
$spell = in_array($pickedspell, explode(',', Lib::user()->spells)); $spell = in_array($pickedspell, explode(',', user()->spells));
if (! $spell) { if (! $spell) {
return 'You have not yet learned this spell. Please go back and try again.'; return 'You have not yet learned this spell. Please go back and try again.';
} }
if (Lib::user()->currentmp < $newspellrow['mp']) { if (user()->currentmp < $newspellrow['mp']) {
return 'You do not have enough Magic Points to cast this spell. Please go back and try again.'; return 'You do not have enough Magic Points to cast this spell. Please go back and try again.';
} }
// Spell type handling (similar to original public static function) // Spell type handling (similar to original public static function)
$page['yourturn'] = self::handleSpellCast($userrow, $newspellrow); $page['yourturn'] = self::handleSpellCast($userrow, $newspellrow);
$page['monsterhp'] = "Monster's HP: ".Lib::user()->currentmonsterhp.'<br><br>'; $page['monsterhp'] = "Monster's HP: ".user()->currentmonsterhp.'<br><br>';
// Check for monster defeat // Check for monster defeat
if (Lib::user()->currentmonsterhp <= 0) { if (user()->currentmonsterhp <= 0) {
Lib::user()->currentmonsterhp = 0; user()->currentmonsterhp = 0;
Lib::user()->save(); user()->save();
Lib::redirect('/victory'); redirect('/victory');
} }
// Monster's turn // Monster's turn
@ -168,7 +167,7 @@ class Fight
// Monster's turn if player lost first swing // Monster's turn if player lost first swing
if (! isset($_POST['run']) && ! isset($_POST['fight']) && ! isset($_POST['spell']) && $chancetoswingfirst == 0) { if (! isset($_POST['run']) && ! isset($_POST['fight']) && ! isset($_POST['spell']) && $chancetoswingfirst == 0) {
$page['yourturn'] = 'The monster attacks before you are ready!<br><br>'; $page['yourturn'] = 'The monster attacks before you are ready!<br><br>';
$page['monsterhp'] = "Monster's HP: ".Lib::user()->currentmonsterhp.'<br><br>'; $page['monsterhp'] = "Monster's HP: ".user()->currentmonsterhp.'<br><br>';
$page['monsterturn'] = self::handleMonsterTurn($userrow, $monster); $page['monsterturn'] = self::handleMonsterTurn($userrow, $monster);
} }
@ -183,7 +182,7 @@ class Fight
</form> </form>
HTML; HTML;
Lib::user()->currentfight += 1; user()->currentfight += 1;
} else { } else {
$page['command'] = <<<HTML $page['command'] = <<<HTML
<b>You have died.</b><br><br> <b>You have died.</b><br><br>
@ -192,24 +191,24 @@ class Fight
HTML; HTML;
} }
Lib::user()->save(); user()->save();
// Finalize page and display it // Finalize page and display it
$page = Lib::render('fight', ['page' => $page]); $page = render('fight', ['page' => $page]);
return $page; return $page;
} }
public static function victory() public static function victory()
{ {
if (Lib::user()->currentmonsterhp != 0) { if (user()->currentmonsterhp != 0) {
Lib::redirect('/fight'); redirect('/fight');
} }
if (Lib::user()->currentfight == 0) { if (user()->currentfight == 0) {
Lib::redirect('/'); redirect('/');
} }
$monsterrow = Lib::get_monster(Lib::user()->currentmonster); $monsterrow = get_monster(user()->currentmonster);
$min = (int) (($monsterrow['maxexp'] / 6) * 5); $min = (int) (($monsterrow['maxexp'] / 6) * 5);
$max = (int) $monsterrow['maxexp']; $max = (int) $monsterrow['maxexp'];
@ -218,8 +217,8 @@ class Fight
$exp = 1; $exp = 1;
} }
if (Lib::user()->expbonus != 0) { if (user()->expbonus != 0) {
$exp += ceil((Lib::user()->expbonus / 100) * $exp); $exp += ceil((user()->expbonus / 100) * $exp);
} }
$min = (int) (($monsterrow['maxgold'] / 6) * 5); $min = (int) (($monsterrow['maxgold'] / 6) * 5);
@ -230,56 +229,56 @@ class Fight
$gold = 1; $gold = 1;
} }
if (Lib::user()->goldbonus != 0) { if (user()->goldbonus != 0) {
$gold += ceil((Lib::user()->goldbonus / 100) * $exp); $gold += ceil((user()->goldbonus / 100) * $exp);
} }
if (Lib::user()->experience + $exp < 16777215) { if (user()->experience + $exp < 16777215) {
$newexp = Lib::user()->experience += $exp; $newexp = user()->experience += $exp;
$warnexp = ''; $warnexp = '';
} else { } else {
$newexp = Lib::user()->experience; $newexp = user()->experience;
$exp = 0; $exp = 0;
$warnexp = 'You have maxed out your experience points.'; $warnexp = 'You have maxed out your experience points.';
} }
if (Lib::user()->gold + $gold < 16777215) { if (user()->gold + $gold < 16777215) {
$newgold = Lib::user()->gold += $gold; $newgold = user()->gold += $gold;
$warngold = ''; $warngold = '';
} else { } else {
$newgold = Lib::user()->gold; $newgold = user()->gold;
$gold = 0; $gold = 0;
$warngold = 'You have maxed out your gold.'; $warngold = 'You have maxed out your gold.';
} }
$levelrow = Lib::db()->query('SELECT * FROM levels WHERE id=? LIMIT 1;', [Lib::user()->level + 1])->fetchArray(SQLITE3_ASSOC); $levelrow = db()->query('SELECT * FROM levels WHERE id=? LIMIT 1;', [user()->level + 1])->fetchArray(SQLITE3_ASSOC);
if (Lib::user()->level < 100) { if (user()->level < 100) {
if ($newexp >= $levelrow[Lib::user()->charclass.'_exp']) { if ($newexp >= $levelrow[user()->charclass.'_exp']) {
Lib::user()->maxhp += $levelrow[Lib::user()->charclass.'_hp']; user()->maxhp += $levelrow[user()->charclass.'_hp'];
Lib::user()->maxmp += $levelrow[Lib::user()->charclass.'_mp']; user()->maxmp += $levelrow[user()->charclass.'_mp'];
Lib::user()->maxtp += $levelrow[Lib::user()->charclass.'_tp']; user()->maxtp += $levelrow[user()->charclass.'_tp'];
Lib::user()->strength += $levelrow[Lib::user()->charclass.'_strength']; user()->strength += $levelrow[user()->charclass.'_strength'];
Lib::user()->dexterity += $levelrow[Lib::user()->charclass.'_dexterity']; user()->dexterity += $levelrow[user()->charclass.'_dexterity'];
Lib::user()->attackpower += $levelrow[Lib::user()->charclass.'_strength']; user()->attackpower += $levelrow[user()->charclass.'_strength'];
Lib::user()->defensepower += $levelrow[Lib::user()->charclass.'_dexterity']; user()->defensepower += $levelrow[user()->charclass.'_dexterity'];
Lib::user()->level += 1; user()->level += 1;
$newlevel = $levelrow['id']; $newlevel = $levelrow['id'];
if ($levelrow[Lib::user()->charclass.'_spells'] != 0) { if ($levelrow[user()->charclass.'_spells'] != 0) {
Lib::user()->spells .= ','.$levelrow[Lib::user()->charclass.'_spells']; user()->spells .= ','.$levelrow[user()->charclass.'_spells'];
$spelltext = 'You have learned a new spell.<br>'; $spelltext = 'You have learned a new spell.<br>';
} else { } else {
$spelltext = ''; $spelltext = '';
$newspell = ''; $newspell = '';
} }
$page = 'Congratulations. You have defeated the '.$monsterrow['name'].".<br>You gain $exp experience. $warnexp <br>You gain $gold gold. $warngold <br><br><b>You have gained a level!</b><br><br>You gain ".$levelrow[Lib::user()->charclass.'_hp'].' hit points.<br>You gain '.$levelrow[Lib::user()->charclass.'_mp'].' magic points.<br>You gain '.$levelrow[Lib::user()->charclass.'_tp'].' travel points.<br>You gain '.$levelrow[Lib::user()->charclass.'_strength'].' strength.<br>You gain '.$levelrow[Lib::user()->charclass.'_dexterity']." dexterity.<br>$spelltext<br>You can now continue <a href=\"/\" hx-get=\"/\" hx-target=\"#middle\">exploring</a>."; $page = 'Congratulations. You have defeated the '.$monsterrow['name'].".<br>You gain $exp experience. $warnexp <br>You gain $gold gold. $warngold <br><br><b>You have gained a level!</b><br><br>You gain ".$levelrow[user()->charclass.'_hp'].' hit points.<br>You gain '.$levelrow[user()->charclass.'_mp'].' magic points.<br>You gain '.$levelrow[user()->charclass.'_tp'].' travel points.<br>You gain '.$levelrow[user()->charclass.'_strength'].' strength.<br>You gain '.$levelrow[user()->charclass.'_dexterity']." dexterity.<br>$spelltext<br>You can now continue <a href=\"/\" hx-get=\"/\" hx-target=\"#middle\">exploring</a>.";
$title = 'Courage and Wit have served thee well!'; $title = 'Courage and Wit have served thee well!';
$dropcode = ''; $dropcode = '';
} else { } else {
$page = 'Congratulations. You have defeated the '.$monsterrow['name'].".<br>You gain $exp experience. $warnexp <br>You gain $gold gold. $warngold <br><br>"; $page = 'Congratulations. You have defeated the '.$monsterrow['name'].".<br>You gain $exp experience. $warnexp <br>You gain $gold gold. $warngold <br><br>";
if (rand(1, 30) === 1) { if (rand(1, 30) === 1) {
$droprow = Lib::db()->query('SELECT * FROM drops WHERE mlevel <= ? ORDER BY RANDOM() LIMIT 1;', [$monsterrow['level']])->fetchArray(SQLITE3_ASSOC); $droprow = db()->query('SELECT * FROM drops WHERE mlevel <= ? ORDER BY RANDOM() LIMIT 1;', [$monsterrow['level']])->fetchArray(SQLITE3_ASSOC);
$dropcode = "dropcode='".$droprow['id']."',"; $dropcode = "dropcode='".$droprow['id']."',";
$page .= 'This monster has dropped an item. <a href="/drop" hx-get="/drop" hx-target="#middle">Click here</a> to reveal and equip the item, or you may also move on and continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>.'; $page .= 'This monster has dropped an item. <a href="/drop" hx-get="/drop" hx-target="#middle">Click here</a> to reveal and equip the item, or you may also move on and continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>.';
} else { } else {
@ -291,26 +290,26 @@ class Fight
} }
} }
Lib::user()->currentaction = 'Exploring'; user()->currentaction = 'Exploring';
Lib::user()->currentfight = 0; user()->currentfight = 0;
Lib::user()->currentuberdamage = 0; user()->currentuberdamage = 0;
Lib::user()->currentuberdefense = 0; user()->currentuberdefense = 0;
Lib::user()->currentmonstersleep = 0; user()->currentmonstersleep = 0;
Lib::user()->currentmonsterimmune = 0; user()->currentmonsterimmune = 0;
Lib::user()->save(); user()->save();
Lib::page_title($title); page_title($title);
return $page; return $page;
} }
public static function drop() public static function drop()
{ {
if (Lib::user()->dropcode == 0) { if (user()->dropcode == 0) {
Lib::redirect('/'); redirect('/');
} }
$droprow = Lib::get_drop(Lib::user()->dropcode); $droprow = get_drop(user()->dropcode);
if (isset($_POST['submit'])) { if (isset($_POST['submit'])) {
$slot = $_POST['slot']; $slot = $_POST['slot'];
@ -320,8 +319,8 @@ class Fight
} }
$slotstr = 'slot'.$slot.'id'; $slotstr = 'slot'.$slot.'id';
if (Lib::user()->$slotstr != 0) { if (user()->$slotstr != 0) {
$slotrow = Lib::get_drop(Lib::user()->$slotstr); $slotrow = get_drop(user()->$slotstr);
$old1 = explode(',', $slotrow['attribute1']); $old1 = explode(',', $slotrow['attribute1']);
if ($slotrow['attribute2'] != 'X') { if ($slotrow['attribute2'] != 'X') {
@ -336,52 +335,52 @@ class Fight
$new2 = [0 => 'maxhp',1 => 0]; $new2 = [0 => 'maxhp',1 => 0];
} }
Lib::user()->$old1[0] -= $old1[1]; user()->$old1[0] -= $old1[1];
Lib::user()->$old2[0] -= $old2[1]; user()->$old2[0] -= $old2[1];
if ($old1[0] == 'strength') { if ($old1[0] == 'strength') {
Lib::user()->attackpower -= $old1[1]; user()->attackpower -= $old1[1];
} }
if ($old1[0] == 'dexterity') { if ($old1[0] == 'dexterity') {
Lib::user()->defensepower -= $old1[1]; user()->defensepower -= $old1[1];
} }
if ($old2[0] == 'strength') { if ($old2[0] == 'strength') {
Lib::user()->attackpower -= $old2[1]; user()->attackpower -= $old2[1];
} }
if ($old2[0] == 'dexterity') { if ($old2[0] == 'dexterity') {
Lib::user()->defensepower -= $old2[1]; user()->defensepower -= $old2[1];
} }
Lib::user()->$new1[0] += $new1[1]; user()->$new1[0] += $new1[1];
Lib::user()->$new2[0] += $new2[1]; user()->$new2[0] += $new2[1];
if ($new1[0] == 'strength') { if ($new1[0] == 'strength') {
Lib::user()->attackpower += $new1[1]; user()->attackpower += $new1[1];
} }
if ($new1[0] == 'dexterity') { if ($new1[0] == 'dexterity') {
Lib::user()->defensepower += $new1[1]; user()->defensepower += $new1[1];
} }
if ($new2[0] == 'strength') { if ($new2[0] == 'strength') {
Lib::user()->attackpower += $new2[1]; user()->attackpower += $new2[1];
} }
if ($new2[0] == 'dexterity') { if ($new2[0] == 'dexterity') {
Lib::user()->defensepower += $new2[1]; user()->defensepower += $new2[1];
} }
if (Lib::user()->currenthp > Lib::user()->maxhp) { if (user()->currenthp > user()->maxhp) {
Lib::user()->currenthp = Lib::user()->maxhp; user()->currenthp = user()->maxhp;
} }
if (Lib::user()->currentmp > Lib::user()->maxmp) { if (user()->currentmp > user()->maxmp) {
Lib::user()->currentmp = Lib::user()->maxmp; user()->currentmp = user()->maxmp;
} }
if (Lib::user()->currenttp > Lib::user()->maxtp) { if (user()->currenttp > user()->maxtp) {
Lib::user()->currenttp = Lib::user()->maxtp; user()->currenttp = user()->maxtp;
} }
$slot_s = 'slot'.$_POST['slot']; $slot_s = 'slot'.$_POST['slot'];
$slot_name = "{$slot_s}name"; $slot_name = "{$slot_s}name";
$slot_id = "{$slot_s}id"; $slot_id = "{$slot_s}id";
Lib::user()->$slot_name = $droprow['name']; user()->$slot_name = $droprow['name'];
Lib::user()->$slot_id = $droprow['id']; user()->$slot_id = $droprow['id'];
} else { } else {
$new1 = explode(',', $droprow['attribute1']); $new1 = explode(',', $droprow['attribute1']);
if ($droprow['attribute2'] != 'X') { if ($droprow['attribute2'] != 'X') {
@ -390,30 +389,30 @@ class Fight
$new2 = [0 => 'maxhp',1 => 0]; $new2 = [0 => 'maxhp',1 => 0];
} }
Lib::user()->$new1[0] += $new1[1]; user()->$new1[0] += $new1[1];
Lib::user()->$new2[0] += $new2[1]; user()->$new2[0] += $new2[1];
if ($new1[0] == 'strength') { if ($new1[0] == 'strength') {
Lib::user()->attackpower += $new1[1]; user()->attackpower += $new1[1];
} }
if ($new1[0] == 'dexterity') { if ($new1[0] == 'dexterity') {
Lib::user()->defensepower += $new1[1]; user()->defensepower += $new1[1];
} }
if ($new2[0] == 'strength') { if ($new2[0] == 'strength') {
Lib::user()->attackpower += $new2[1]; user()->attackpower += $new2[1];
} }
if ($new2[0] == 'dexterity') { if ($new2[0] == 'dexterity') {
Lib::user()->defensepower += $new2[1]; user()->defensepower += $new2[1];
} }
$slot_s = 'slot'.$_POST['slot']; $slot_s = 'slot'.$_POST['slot'];
$slot_name = "{$slot_s}name"; $slot_name = "{$slot_s}name";
$slot_id = "{$slot_s}id"; $slot_id = "{$slot_s}id";
Lib::user()->$slot_name = $droprow['name']; user()->$slot_name = $droprow['name'];
Lib::user()->$slot_id = $droprow['id']; user()->$slot_id = $droprow['id'];
} }
Lib::user()->save(); user()->save();
return 'The item has been equipped. You can now continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>.'; return 'The item has been equipped. You can now continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>.';
} }
@ -450,7 +449,7 @@ class Fight
} }
$page .= '<br>Select an inventory slot from the list below to equip this item. If the inventory slot is already full, the old item will be discarded.'; $page .= '<br>Select an inventory slot from the list below to equip this item. If the inventory slot is already full, the old item will be discarded.';
$page .= '<form action="/drop" method="post"><select name="slot"><option value="0">Choose One</option><option value="1">Slot 1: '.Lib::user()->slot1name.'</option><option value="2">Slot 2: '.Lib::user()->slot2name.'</option><option value="3">Slot 3: '.Lib::user()->slot3name.'</option></select> <input type="submit" name="submit" value="Submit" /></form>'; $page .= '<form action="/drop" method="post"><select name="slot"><option value="0">Choose One</option><option value="1">Slot 1: '.user()->slot1name.'</option><option value="2">Slot 2: '.user()->slot2name.'</option><option value="3">Slot 3: '.user()->slot3name.'</option></select> <input type="submit" name="submit" value="Submit" /></form>';
$page .= 'You may also choose to just continue <a href="/" hx-get="/" hx-target="#middle">exploring</a> and give up this item.'; $page .= 'You may also choose to just continue <a href="/" hx-get="/" hx-target="#middle">exploring</a> and give up this item.';
return $page; return $page;
@ -469,40 +468,40 @@ class Fight
public static function handleMonsterTurn(&$userrow, $monsterrow) public static function handleMonsterTurn(&$userrow, $monsterrow)
{ {
$pagearray = ''; $pagearray = '';
if (Lib::user()->currentmonstersleep != 0) { if (user()->currentmonstersleep != 0) {
$chancetowake = rand(1, 15); $chancetowake = rand(1, 15);
if ($chancetowake > Lib::user()->currentmonstersleep) { if ($chancetowake > user()->currentmonstersleep) {
Lib::user()->currentmonstersleep = 0; user()->currentmonstersleep = 0;
$pagearray .= 'The monster has woken up.<br>'; $pagearray .= 'The monster has woken up.<br>';
} else { } else {
$pagearray .= 'The monster is still asleep.<br>'; $pagearray .= 'The monster is still asleep.<br>';
} }
} }
if (Lib::user()->currentmonstersleep == 0) { if (user()->currentmonstersleep == 0) {
$tohit = (int) ceil(mt_rand((int) ($monsterrow['maxdam'] * 0.5), (int) $monsterrow['maxdam'])); $tohit = (int) ceil(mt_rand((int) ($monsterrow['maxdam'] * 0.5), (int) $monsterrow['maxdam']));
$toblock = (int) ceil(mt_rand((int) (Lib::user()->defensepower * 0.75), (int) Lib::user()->defensepower) / 4); $toblock = (int) ceil(mt_rand((int) (user()->defensepower * 0.75), (int) user()->defensepower) / 4);
$tododge = rand(1, 150); $tododge = rand(1, 150);
if ($tododge <= sqrt(Lib::user()->dexterity)) { if ($tododge <= sqrt(user()->dexterity)) {
$tohit = 0; $tohit = 0;
$pagearray .= "You dodge the monster's attack. No damage has been scored.<br>"; $pagearray .= "You dodge the monster's attack. No damage has been scored.<br>";
$persondamage = 0; $persondamage = 0;
} else { } else {
$persondamage = max(1, $tohit - $toblock); $persondamage = max(1, $tohit - $toblock);
if (Lib::user()->currentuberdefense != 0) { if (user()->currentuberdefense != 0) {
$persondamage -= (int) ceil($persondamage * (Lib::user()->currentuberdefense / 100)); $persondamage -= (int) ceil($persondamage * (user()->currentuberdefense / 100));
} }
$persondamage = max(1, $persondamage); $persondamage = max(1, $persondamage);
} }
$pagearray .= "The monster attacks you for $persondamage damage.<br><br>"; $pagearray .= "The monster attacks you for $persondamage damage.<br><br>";
Lib::user()->currenthp -= $persondamage; user()->currenthp -= $persondamage;
if (Lib::user()->currenthp <= 0) { if (user()->currenthp <= 0) {
$newgold = (int) ceil(Lib::user()->gold / 2); $newgold = (int) ceil(user()->gold / 2);
$newhp = (int) ceil(Lib::user()->maxhp / 4); $newhp = (int) ceil(user()->maxhp / 4);
Lib::db()->query("UPDATE users SET currenthp=?, currentaction='In Town', currentmonster=0, currentmonsterhp=0, currentmonstersleep=0, currentmonsterimmune=0, currentfight=0, latitude=0, longitude=0, gold=? WHERE id=?;", [ db()->query("UPDATE users SET currenthp=?, currentaction='In Town', currentmonster=0, currentmonsterhp=0, currentmonstersleep=0, currentmonsterimmune=0, currentfight=0, latitude=0, longitude=0, gold=? WHERE id=?;", [
$newhp, $newgold, $userrow['id'], $newhp, $newgold, $userrow['id'],
]); ]);
self::dead(); self::dead();
@ -517,38 +516,38 @@ class Fight
$pagearray = ''; $pagearray = '';
switch ($newspellrow['type']) { switch ($newspellrow['type']) {
case 1: // Heal spell case 1: // Heal spell
$newhp = min(Lib::user()->currenthp + $newspellrow['attribute'], Lib::user()->maxhp); $newhp = min(user()->currenthp + $newspellrow['attribute'], user()->maxhp);
Lib::user()->currenthp = $newhp; user()->currenthp = $newhp;
Lib::user()->currentmp -= $newspellrow['mp']; user()->currentmp -= $newspellrow['mp'];
$pagearray = "You have cast the {$newspellrow['name']} spell, and gained {$newspellrow['attribute']} Hit Points.<br><br>"; $pagearray = "You have cast the {$newspellrow['name']} spell, and gained {$newspellrow['attribute']} Hit Points.<br><br>";
break; break;
case 2: // Hurt spell case 2: // Hurt spell
if (Lib::user()->currentmonsterimmune == 0) { if (user()->currentmonsterimmune == 0) {
$monsterdamage = mt_rand((int) (($newspellrow['attribute'] / 6) * 5), $newspellrow['attribute']); $monsterdamage = mt_rand((int) (($newspellrow['attribute'] / 6) * 5), $newspellrow['attribute']);
Lib::user()->currentmonsterhp -= $monsterdamage; user()->currentmonsterhp -= $monsterdamage;
$pagearray = "You have cast the {$newspellrow['name']} spell for $monsterdamage damage.<br><br>"; $pagearray = "You have cast the {$newspellrow['name']} spell for $monsterdamage damage.<br><br>";
} else { } else {
$pagearray = "You have cast the {$newspellrow['name']} spell, but the monster is immune to it.<br><br>"; $pagearray = "You have cast the {$newspellrow['name']} spell, but the monster is immune to it.<br><br>";
} }
Lib::user()->currentmp -= $newspellrow['mp']; user()->currentmp -= $newspellrow['mp'];
break; break;
case 3: // Sleep spell case 3: // Sleep spell
if (Lib::user()->currentmonsterimmune != 2) { if (user()->currentmonsterimmune != 2) {
Lib::user()->currentmonstersleep = $newspellrow['attribute']; user()->currentmonstersleep = $newspellrow['attribute'];
$pagearray = "You have cast the {$newspellrow['name']} spell. The monster is asleep.<br><br>"; $pagearray = "You have cast the {$newspellrow['name']} spell. The monster is asleep.<br><br>";
} else { } else {
$pagearray = "You have cast the {$newspellrow['name']} spell, but the monster is immune to it.<br><br>"; $pagearray = "You have cast the {$newspellrow['name']} spell, but the monster is immune to it.<br><br>";
} }
Lib::user()->currentmp -= $newspellrow['mp']; user()->currentmp -= $newspellrow['mp'];
break; break;
case 4: // +Damage spell case 4: // +Damage spell
Lib::user()->currentuberdamage = $newspellrow['attribute']; user()->currentuberdamage = $newspellrow['attribute'];
Lib::user()->currentmp -= $newspellrow['mp']; user()->currentmp -= $newspellrow['mp'];
$pagearray = "You have cast the {$newspellrow['name']} spell, and will gain {$newspellrow['attribute']}% damage until the end of this fight.<br><br>"; $pagearray = "You have cast the {$newspellrow['name']} spell, and will gain {$newspellrow['attribute']}% damage until the end of this fight.<br><br>";
break; break;
case 5: // +Defense spell case 5: // +Defense spell
Lib::user()->currentuberdefense = $newspellrow['attribute']; user()->currentuberdefense = $newspellrow['attribute'];
Lib::user()->currentmp -= $newspellrow['mp']; user()->currentmp -= $newspellrow['mp'];
$pagearray = "You have cast the {$newspellrow['name']} spell, and will gain {$newspellrow['attribute']}% defense until the end of this fight.<br><br>"; $pagearray = "You have cast the {$newspellrow['name']} spell, and will gain {$newspellrow['attribute']}% defense until the end of this fight.<br><br>";
break; break;
} }

View File

@ -13,7 +13,6 @@ declare(strict_types=1);
namespace DragonKnight\Actions; namespace DragonKnight\Actions;
use DragonKnight\Lib;
use DragonKnight\Router; use DragonKnight\Router;
class Forum class Forum
@ -31,7 +30,7 @@ class Forum
public static function donothing($start = 0) public static function donothing($start = 0)
{ {
$query = Lib::db()->query('SELECT * FROM forum WHERE parent=0 ORDER BY newpostdate DESC LIMIT 20 OFFSET ?;', [20 * $start]); $query = db()->query('SELECT * FROM forum WHERE parent=0 ORDER BY newpostdate DESC LIMIT 20 OFFSET ?;', [20 * $start]);
$page = <<<HTML $page = <<<HTML
<table width="100%"> <table width="100%">
<tr> <tr>
@ -68,45 +67,45 @@ class Forum
$page .= '</table></td></tr></table>'; $page .= '</table></td></tr></table>';
Lib::page_title('Forum'); page_title('Forum');
return $page; return $page;
} }
public static function showthread($id, $start) public static function showthread($id, $start)
{ {
$posts = Lib::db()->query('SELECT * FROM forum WHERE id=? OR parent=? ORDER BY id LIMIT 15 OFFSET ?;', [$id, $id, $start * 15]); $posts = db()->query('SELECT * FROM forum WHERE id=? OR parent=? ORDER BY id LIMIT 15 OFFSET ?;', [$id, $id, $start * 15]);
$title = Lib::db()->query('SELECT title FROM forum WHERE id=? LIMIT 1;', [$id])->fetchArray(SQLITE3_ASSOC); $title = db()->query('SELECT title FROM forum WHERE id=? LIMIT 1;', [$id])->fetchArray(SQLITE3_ASSOC);
$page = '<table width="100%"><tr><td style="padding:1px; background-color:black;"><table width="100%" style="margins:0px;" cellspacing="1" cellpadding="3"><tr><td colspan="2" style="background-color:#dddddd;"><b><a href="/forum" hx-get="/forum" hx-target="#middle">Forum</a> :: '.$title['title']."</b></td></tr>\n"; $page = '<table width="100%"><tr><td style="padding:1px; background-color:black;"><table width="100%" style="margins:0px;" cellspacing="1" cellpadding="3"><tr><td colspan="2" style="background-color:#dddddd;"><b><a href="/forum" hx-get="/forum" hx-target="#middle">Forum</a> :: '.$title['title']."</b></td></tr>\n";
while ($row = $posts->fetchArray(SQLITE3_ASSOC)) { while ($row = $posts->fetchArray(SQLITE3_ASSOC)) {
$page .= '<tr><td width="25%" style="background-color:#ffffff; vertical-align:top;"><span class="small"><b>'.$row['author'].'</b><br><br>'.Lib::pretty_date($row['postdate']).'</td><td style="background-color:#ffffff; vertical-align:top;">'.nl2br($row['content'])."</td></tr>\n"; $page .= '<tr><td width="25%" style="background-color:#ffffff; vertical-align:top;"><span class="small"><b>'.$row['author'].'</b><br><br>'.pretty_date($row['postdate']).'</td><td style="background-color:#ffffff; vertical-align:top;">'.nl2br($row['content'])."</td></tr>\n";
} }
$page .= '</table></td></tr></table><br>'; $page .= '</table></td></tr></table><br>';
$page .= "<table width=\"100%\"><tr><td><b>Reply To This Thread:</b><br><form action=\"/forum/reply\" method=\"post\" hx-post=\"/forum/reply\" hx-target=\"#middle\"><input type=\"hidden\" name=\"parent\" value=\"$id\" /><input type=\"hidden\" name=\"title\" value=\"Re: ".$title['title'].'" /><textarea name="content" rows="7" cols="40"></textarea><br><input type="submit" name="submit" value="Submit" /> <input type="reset" name="reset" value="Reset" /></form></td></tr></table>'; $page .= "<table width=\"100%\"><tr><td><b>Reply To This Thread:</b><br><form action=\"/forum/reply\" method=\"post\" hx-post=\"/forum/reply\" hx-target=\"#middle\"><input type=\"hidden\" name=\"parent\" value=\"$id\" /><input type=\"hidden\" name=\"title\" value=\"Re: ".$title['title'].'" /><textarea name="content" rows="7" cols="40"></textarea><br><input type="submit" name="submit" value="Submit" /> <input type="reset" name="reset" value="Reset" /></form></td></tr></table>';
Lib::page_title('Forum: '.$title['title']); page_title('Forum: '.$title['title']);
return $page; return $page;
} }
public static function reply() public static function reply()
{ {
$form = Lib::validate($_POST, [ $form = validate($_POST, [
'title' => [], 'title' => [],
'content' => [], 'content' => [],
]); ]);
if (! $form['valid']) { if (! $form['valid']) {
exit(Lib::ul_from_validate_errors($form['errors'])); exit(ul_from_validate_errors($form['errors']));
} }
$form = $form['data']; $form = $form['data'];
Lib::db()->query('INSERT INTO forum (author, title, content, parent) VALUES (?, ?, ?, ?);', [ db()->query('INSERT INTO forum (author, title, content, parent) VALUES (?, ?, ?, ?);', [
Lib::user()->username, $form['title'], $form['content'], $form['parent'], user()->username, $form['title'], $form['content'], $form['parent'],
]); ]);
Lib::db()->query('UPDATE forum SET newpostdate=CURRENT_TIMESTAMP, replies=replies + 1 WHERE id=?;', [$form['parent']]); db()->query('UPDATE forum SET newpostdate=CURRENT_TIMESTAMP, replies=replies + 1 WHERE id=?;', [$form['parent']]);
return self::showthread($form['parent'], 0); return self::showthread($form['parent'], 0);
} }
@ -114,23 +113,23 @@ class Forum
public static function newthread() public static function newthread()
{ {
if (isset($_POST['submit'])) { if (isset($_POST['submit'])) {
$form = Lib::validate($_POST, [ $form = validate($_POST, [
'title' => ['length:2-30'], 'title' => ['length:2-30'],
'content' => [], 'content' => [],
]); ]);
if (! $form['valid']) { if (! $form['valid']) {
exit(Lib::ul_from_validate_errors($form['errors'])); exit(ul_from_validate_errors($form['errors']));
} }
$form = $form['data']; $form = $form['data'];
Lib::db()->query('INSERT INTO forum (author, title, content) VALUES (?, ?, ?);', [ db()->query('INSERT INTO forum (author, title, content) VALUES (?, ?, ?);', [
Lib::user()->username, $form['title'], $form['content'], user()->username, $form['title'], $form['content'],
]); ]);
Lib::redirect('/forum/thread/'.Lib::db()->lastInsertRowID().'/0'); redirect('/forum/thread/'.db()->lastInsertRowID().'/0');
} }
Lib::page_title('Form: New Thread'); page_title('Form: New Thread');
return '<table width="100%"><tr><td><b>Make A New Post:</b><br><br/ ><form action="/forum/new" method="post" hx-post="/forum/new" hx-target="#middle">Title:<br><input type="text" name="title" size="50" maxlength="50" /><br><br>Message:<br><textarea name="content" rows="7" cols="40"></textarea><br><br><input type="submit" name="submit" value="Submit" /> <input type="reset" name="reset" value="Reset" /></form></td></tr></table>'; return '<table width="100%"><tr><td><b>Make A New Post:</b><br><br/ ><form action="/forum/new" method="post" hx-post="/forum/new" hx-target="#middle">Title:<br><input type="text" name="title" size="50" maxlength="50" /><br><br>Message:<br><textarea name="content" rows="7" cols="40"></textarea><br><br><input type="submit" name="submit" value="Submit" /> <input type="reset" name="reset" value="Reset" /></form></td></tr></table>';
} }

View File

@ -13,14 +13,12 @@ declare(strict_types=1);
namespace DragonKnight\Actions; namespace DragonKnight\Actions;
use DragonKnight\Lib;
class Heal class Heal
{ {
public static function healspells(int $id): string public static function healspells(int $id): string
{ {
$user_spells = Lib::user()->spells(); $user_spells = user()->spells();
$spell = Lib::get_spell($id); $spell = get_spell($id);
$has_spell = false; $has_spell = false;
foreach ($user_spells as $us) { foreach ($user_spells as $us) {
if ($us['id'] === $id) { if ($us['id'] === $id) {
@ -32,23 +30,23 @@ class Heal
$page = 'You have not yet learned this spell. Please go back and try again.'; $page = 'You have not yet learned this spell. Please go back and try again.';
} elseif ($spell['type'] !== 1) { } elseif ($spell['type'] !== 1) {
$page = 'This is not a healing spell. Please go back and try again.'; $page = 'This is not a healing spell. Please go back and try again.';
} elseif (Lib::user()->currentmp < $spell['mp']) { } elseif (user()->currentmp < $spell['mp']) {
$page = 'You do not have enough Magic Points to cast this spell. Please go back and try again.'; $page = 'You do not have enough Magic Points to cast this spell. Please go back and try again.';
} elseif (Lib::user()->currentaction === 'Fighting') { } elseif (user()->currentaction === 'Fighting') {
$page = 'You cannot use the Quick Spells list during a fight. Please go back and select the Healing Spell you wish to use from the Spells box on the main fighting screen to continue.'; $page = 'You cannot use the Quick Spells list during a fight. Please go back and select the Healing Spell you wish to use from the Spells box on the main fighting screen to continue.';
} elseif (Lib::user()->currenthp == Lib::user()->maxhp) { } elseif (user()->currenthp == user()->maxhp) {
$page = 'Your HP is already full. You don\'t need to use a Healing spell now.'; $page = 'Your HP is already full. You don\'t need to use a Healing spell now.';
} else { } else {
$restored = Lib::user()->restore_hp($spell['attribute']); $restored = user()->restore_hp($spell['attribute']);
Lib::user()->currentmp -= $spell['mp']; user()->currentmp -= $spell['mp'];
Lib::user()->save(); user()->save();
$page = <<<HTML $page = <<<HTML
You have cast the {$spell['name']} spell, and gained {$restored} HP. You can now continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>. You have cast the {$spell['name']} spell, and gained {$restored} HP. You can now continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>.
HTML; HTML;
} }
Lib::page_title('Casting '.$spell['name']); page_title('Casting '.$spell['name']);
return $page; return $page;
} }

View File

@ -13,7 +13,6 @@ declare(strict_types=1);
namespace DragonKnight\Actions; namespace DragonKnight\Actions;
use DragonKnight\Lib;
use DragonKnight\Router; use DragonKnight\Router;
class Help class Help
@ -270,7 +269,7 @@ class Help
<tr><td><b>Type</b></td><td><b>Name</b></td><td><b>Cost</b></td><td><b>Attribute</b></td><td><b>Special</b></td></tr> <tr><td><b>Type</b></td><td><b>Name</b></td><td><b>Cost</b></td><td><b>Attribute</b></td><td><b>Special</b></td></tr>
HTML; HTML;
$items = Lib::db()->query('SELECT * FROM items ORDER BY id;'); $items = db()->query('SELECT * FROM items ORDER BY id;');
$item_types = [1 => ['weapon', 'Attack'], 2 => ['armor', 'Defense'], 3 => ['shield', 'Defense']]; $item_types = [1 => ['weapon', 'Attack'], 2 => ['armor', 'Defense'], 3 => ['shield', 'Defense']];
while ($item = $items->fetchArray(SQLITE3_ASSOC)) { while ($item = $items->fetchArray(SQLITE3_ASSOC)) {
@ -279,7 +278,7 @@ class Help
if ($item['special'] !== 'X') { if ($item['special'] !== 'X') {
$special = explode(',', $item['special']); $special = explode(',', $item['special']);
$attr = Lib::special_to_string($special[0]); $attr = special_to_string($special[0]);
$stat = (($special[1] > 0) ? '+' : '').$special[1]; $stat = (($special[1] > 0) ? '+' : '').$special[1];
$bigspecial = "$attr $stat"; $bigspecial = "$attr $stat";
} else { } else {
@ -299,12 +298,12 @@ class Help
<tr><td><b>Name</b></td><td><b>Monster Level</b></td><td><b>Attribute 1</b></td><td><b>Attribute 2</b></td></tr> <tr><td><b>Name</b></td><td><b>Monster Level</b></td><td><b>Attribute 1</b></td><td><b>Attribute 2</b></td></tr>
HTML; HTML;
$drops = Lib::db()->query('SELECT * FROM drops ORDER BY id;'); $drops = db()->query('SELECT * FROM drops ORDER BY id;');
while ($drop = $drops->fetchArray(SQLITE3_ASSOC)) { while ($drop = $drops->fetchArray(SQLITE3_ASSOC)) {
if ($drop['attribute1'] !== 'X') { if ($drop['attribute1'] !== 'X') {
$special = explode(',', $drop['attribute1']); $special = explode(',', $drop['attribute1']);
$attr = Lib::special_to_string($special[0]); $attr = special_to_string($special[0]);
$stat = (($special[1] > 0) ? '+' : '').$special[1]; $stat = (($special[1] > 0) ? '+' : '').$special[1];
$bigspecial1 = "$attr $stat"; $bigspecial1 = "$attr $stat";
} else { } else {
@ -313,7 +312,7 @@ class Help
if ($drop['attribute2'] !== 'X') { if ($drop['attribute2'] !== 'X') {
$special = explode(',', $drop['attribute2']); $special = explode(',', $drop['attribute2']);
$attr = Lib::special_to_string($special[0]); $attr = special_to_string($special[0]);
$stat = (($special[1] > 0) ? '+' : '').$special[1]; $stat = (($special[1] > 0) ? '+' : '').$special[1];
$bigspecial2 = "$attr $stat"; $bigspecial2 = "$attr $stat";
} else { } else {
@ -335,7 +334,7 @@ class Help
<tr><td><b>Name</b></td><td><b>Cost</b></td><td><b>Type</b></td><td><b>Attribute</b></td></tr> <tr><td><b>Name</b></td><td><b>Cost</b></td><td><b>Type</b></td><td><b>Attribute</b></td></tr>
HTML; HTML;
$spells = Lib::db()->query('SELECT * FROM spells ORDER BY id;'); $spells = db()->query('SELECT * FROM spells ORDER BY id;');
$spell_types = ['None', 'Heal', 'Hurt', 'Sleep', '+Damage (%)', '+Defense (%)']; $spell_types = ['None', 'Heal', 'Hurt', 'Sleep', '+Damage (%)', '+Defense (%)'];
while ($spell = $spells->fetchArray(SQLITE3_ASSOC)) { while ($spell = $spells->fetchArray(SQLITE3_ASSOC)) {
$page .= <<<HTML $page .= <<<HTML
@ -368,7 +367,7 @@ class Help
<tr><td><b>Name</b></td><td><b>Max HP</b></td><td><b>Max Damage</b></td><td><b>Armor</b></td><td><b>Level</b></td><td><b>Max Exp.</b></td><td><b>Max Gold</b></td><td><b>Immunity</b></td></tr> <tr><td><b>Name</b></td><td><b>Max HP</b></td><td><b>Max Damage</b></td><td><b>Armor</b></td><td><b>Level</b></td><td><b>Max Exp.</b></td><td><b>Max Gold</b></td><td><b>Immunity</b></td></tr>
HTML; HTML;
$monsters = Lib::db()->query('SELECT * FROM monsters ORDER BY id;'); $monsters = db()->query('SELECT * FROM monsters ORDER BY id;');
$immunities = ['<span class="light">None</span>', 'Hurt', 'Hurt & Sleep']; $immunities = ['<span class="light">None</span>', 'Hurt', 'Hurt & Sleep'];
while ($m = $monsters->fetchArray(SQLITE3_ASSOC)) { while ($m = $monsters->fetchArray(SQLITE3_ASSOC)) {
@ -383,7 +382,7 @@ class Help
{ {
$rows = []; $rows = [];
$levels = Lib::db()->query('SELECT * FROM levels ORDER BY id;'); $levels = db()->query('SELECT * FROM levels ORDER BY id;');
while ($level = $levels->fetchArray(SQLITE3_ASSOC)) { while ($level = $levels->fetchArray(SQLITE3_ASSOC)) {
$class_data = [1 => [], 2 => [], 3 => []]; $class_data = [1 => [], 2 => [], 3 => []];
@ -405,7 +404,7 @@ class Help
} }
$spells = []; $spells = [];
$spells_query = Lib::db()->query('SELECT * FROM spells ORDER BY id;'); $spells_query = db()->query('SELECT * FROM spells ORDER BY id;');
while ($spell = $spells_query->fetchArray(SQLITE3_ASSOC)) { while ($spell = $spells_query->fetchArray(SQLITE3_ASSOC)) {
$spells[$spell['id']] = $spell; $spells[$spell['id']] = $spell;
} }
@ -508,7 +507,7 @@ class Help
public static function display_help(string $content) public static function display_help(string $content)
{ {
return Lib::render('layouts/help', [ return render('layouts/help', [
'content' => $content, 'content' => $content,
'version' => VERSION, 'version' => VERSION,
'build' => BUILD, 'build' => BUILD,

View File

@ -13,9 +13,13 @@ declare(strict_types=1);
namespace DragonKnight\Actions; namespace DragonKnight\Actions;
use DragonKnight\Lib;
use DragonKnight\Router; use DragonKnight\Router;
use function DragonKnight\db;
use function DragonKnight\ul_from_validate_errors;
use function DragonKnight\user;
use function DragonKnight\validate;
class Install class Install
{ {
public static function register_routes(Router $r): Router public static function register_routes(Router $r): Router
@ -56,20 +60,20 @@ class Install
*/ */
public static function second() public static function second()
{ {
if (! is_dir($path = getcwd().'/db')) { if (! is_dir($path = getcwd().'/db')) {
if (mkdir($path, 0777, true) === false) { if (mkdir($path, 0777, true) === false) {
throw new \Exception('Failed to create database directory at '.$path.'. Please check permissions.'); throw new \Exception('Failed to create database directory at '.$path.'. Please check permissions.');
} }
} }
if (file_exists($path = getcwd().'/db/database.db')) { if (file_exists($path = getcwd().'/db/database.db')) {
if (unlink($path) === false) { if (unlink($path) === false) {
throw new \Exception('Failed to delete existing database file at '.$path.'. Please check permissions.'); throw new \Exception('Failed to delete existing database file at '.$path.'. Please check permissions.');
} }
} }
$page = '<html><head><title>Dragon Knight Installation</title></head><body><b>Dragon Knight Installation: Page Two</b><br><br>'; $page = '<html><head><title>Dragon Knight Installation</title></head><body><b>Dragon Knight Installation: Page Two</b><br><br>';
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE babble ( CREATE TABLE babble (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`posttime` TEXT NOT NULL DEFAULT '00:00:00', `posttime` TEXT NOT NULL DEFAULT '00:00:00',
@ -80,7 +84,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Babble', 'create'); $page .= self::table_status_msg($query === true, 'Babble', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE drops ( CREATE TABLE drops (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '',
@ -93,7 +97,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Drops', 'create'); $page .= self::table_status_msg($query === true, 'Drops', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
INSERT INTO drops VALUES INSERT INTO drops VALUES
(1, 'Life Pebble', 1, 1, 'maxhp,10', 'X'), (1, 'Life Pebble', 1, 1, 'maxhp,10', 'X'),
(2, 'Life Stone', 10, 1, 'maxhp,25', 'X'), (2, 'Life Stone', 10, 1, 'maxhp,25', 'X'),
@ -131,7 +135,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Drops', 'populate'); $page .= self::table_status_msg($query === true, 'Drops', 'populate');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE forum ( CREATE TABLE forum (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`postdate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `postdate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -146,7 +150,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Forum', 'create'); $page .= self::table_status_msg($query === true, 'Forum', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE items ( CREATE TABLE items (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`type` INTEGER NOT NULL DEFAULT 0, `type` INTEGER NOT NULL DEFAULT 0,
@ -159,7 +163,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Items', 'create'); $page .= self::table_status_msg($query === true, 'Items', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
INSERT INTO items VALUES INSERT INTO items VALUES
(1, 1, 'Stick', 10, 2, 'X'), (1, 1, 'Stick', 10, 2, 'X'),
(2, 1, 'Branch', 30, 4, 'X'), (2, 1, 'Branch', 30, 4, 'X'),
@ -198,7 +202,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Drops', 'populate'); $page .= self::table_status_msg($query === true, 'Drops', 'populate');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE classes ( CREATE TABLE classes (
'id' INTEGER PRIMARY KEY AUTOINCREMENT, 'id' INTEGER PRIMARY KEY AUTOINCREMENT,
'name' TEXT NOT NULL, 'name' TEXT NOT NULL,
@ -217,7 +221,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Classes', 'create'); $page .= self::table_status_msg($query === true, 'Classes', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
INSERT INTO classes VALUES INSERT INTO classes VALUES
(0, 'Adventurer', '', 3, 15, 10, 4, 4, 2, 2, 2, 2), (0, 'Adventurer', '', 3, 15, 10, 4, 4, 2, 2, 2, 2),
(1, 'Mage', '', 1, 10, 15, 1, 7, 1, 3, 1, 2), (1, 'Mage', '', 1, 10, 15, 1, 7, 1, 3, 1, 2),
@ -227,7 +231,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Classes', 'populate'); $page .= self::table_status_msg($query === true, 'Classes', 'populate');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE levels ( CREATE TABLE levels (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`1_exp` INTEGER NOT NULL DEFAULT 0, `1_exp` INTEGER NOT NULL DEFAULT 0,
@ -256,7 +260,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Levels', 'create'); $page .= self::table_status_msg($query === true, 'Levels', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
INSERT INTO levels VALUES INSERT INTO levels VALUES
(1, 0, 15, 0, 5, 5, 5, 0, 0, 15, 0, 5, 5, 5, 0, 0, 15, 0, 5, 5, 5, 0), (1, 0, 15, 0, 5, 5, 5, 0, 0, 15, 0, 5, 5, 5, 0, 0, 15, 0, 5, 5, 5, 0),
(2, 15, 2, 5, 1, 0, 1, 1, 18, 2, 4, 1, 2, 1, 1, 20, 2, 5, 1, 0, 2, 1), (2, 15, 2, 5, 1, 0, 1, 1, 18, 2, 4, 1, 2, 1, 1, 20, 2, 5, 1, 0, 2, 1),
@ -362,7 +366,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Levels', 'populate'); $page .= self::table_status_msg($query === true, 'Levels', 'populate');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE monsters ( CREATE TABLE monsters (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '',
@ -378,7 +382,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Monsters', 'create'); $page .= self::table_status_msg($query === true, 'Monsters', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
INSERT INTO monsters VALUES INSERT INTO monsters VALUES
(1, 'Blue Slime', 4, 3, 1, 1, 1, 1, 0), (1, 'Blue Slime', 4, 3, 1, 1, 1, 1, 0),
(2, 'Red Slime', 6, 5, 1, 1, 2, 1, 0), (2, 'Red Slime', 6, 5, 1, 1, 2, 1, 0),
@ -535,7 +539,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Monsters', 'populate'); $page .= self::table_status_msg($query === true, 'Monsters', 'populate');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE news ( CREATE TABLE news (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`author` TEXT NOT NULL DEFAULT 'Guild Master', `author` TEXT NOT NULL DEFAULT 'Guild Master',
@ -546,11 +550,11 @@ class Install
$page .= self::table_status_msg($query === true, 'News', 'create'); $page .= self::table_status_msg($query === true, 'News', 'create');
$query = Lib::db()->exec("INSERT INTO news (content) VALUES ('This is the first news post. Please use the admin control panel to add another one and make this one go away.');"); $query = db()->exec("INSERT INTO news (content) VALUES ('This is the first news post. Please use the admin control panel to add another one and make this one go away.');");
$page .= self::table_status_msg($query === true, 'News', 'populate'); $page .= self::table_status_msg($query === true, 'News', 'populate');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE spells ( CREATE TABLE spells (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL, `name` TEXT NOT NULL,
@ -562,7 +566,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Spells', 'create'); $page .= self::table_status_msg($query === true, 'Spells', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
INSERT INTO spells VALUES INSERT INTO spells VALUES
(1, 'Heal', 5, 10, 1), (1, 'Heal', 5, 10, 1),
(2, 'Revive', 10, 25, 1), (2, 'Revive', 10, 25, 1),
@ -587,7 +591,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Spells', 'populate'); $page .= self::table_status_msg($query === true, 'Spells', 'populate');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE towns ( CREATE TABLE towns (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL, `name` TEXT NOT NULL,
@ -602,7 +606,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Towns', 'create'); $page .= self::table_status_msg($query === true, 'Towns', 'create');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
INSERT INTO towns VALUES INSERT INTO towns VALUES
(1, 'Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19,28,29'), (1, 'Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19,28,29'),
(2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'), (2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'),
@ -616,7 +620,7 @@ class Install
$page .= self::table_status_msg($query === true, 'Towns', 'populate'); $page .= self::table_status_msg($query === true, 'Towns', 'populate');
$query = Lib::db()->exec(<<<SQL $query = db()->exec(<<<SQL
CREATE TABLE users ( CREATE TABLE users (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`username` TEXT NOT NULL, `username` TEXT NOT NULL,
@ -714,7 +718,7 @@ class Install
public static function fourth(?array $post = null) public static function fourth(?array $post = null)
{ {
$post ??= $_POST; $post ??= $_POST;
$form = Lib::validate($post, [ $form = validate($post, [
'username' => ['length:3-18', 'alpha-spaces'], 'username' => ['length:3-18', 'alpha-spaces'],
'email' => ['email'], 'email' => ['email'],
'confirm_email' => ['confirm'], 'confirm_email' => ['confirm'],
@ -723,11 +727,11 @@ class Install
]); ]);
if (! $form['valid']) { if (! $form['valid']) {
exit(Lib::ul_from_validate_errors($form['errors'])); exit(ul_from_validate_errors($form['errors']));
} }
$form = $form['data']; $form = $form['data'];
if (Lib::db()->query( if (db()->query(
"INSERT INTO users (username, password, email, verify, charclass, authlevel) VALUES (?, ?, ?, 'g2g', ?, 1)", "INSERT INTO users (username, password, email, verify, charclass, authlevel) VALUES (?, ?, ?, 'g2g', ?, 1)",
[$form['username'], password_hash($form['password'], PASSWORD_ARGON2ID), $form['email'], $form['charclass'] ?? null] [$form['username'], password_hash($form['password'], PASSWORD_ARGON2ID), $form['email'], $form['charclass'] ?? null]
) === false) { ) === false) {
@ -788,7 +792,7 @@ class Install
}; };
if ($condition === false) { if ($condition === false) {
return "Error {$verb[1]} $table_name table. (".Lib::db()->lastErrorMsg().')<br>'; return "Error {$verb[1]} $table_name table. (".db()->lastErrorMsg().')<br>';
} }
return "$table_name table {$verb[0]}.<br>"; return "$table_name table {$verb[0]}.<br>";

View File

@ -13,9 +13,19 @@ declare(strict_types=1);
namespace DragonKnight\Actions; namespace DragonKnight\Actions;
use DragonKnight\Lib;
use DragonKnight\Router; use DragonKnight\Router;
use function DragonKnight\db;
use function DragonKnight\env;
use function DragonKnight\get_town_by_xy;
use function DragonKnight\get_item;
use function DragonKnight\get_town_by_id;
use function DragonKnight\is_post;
use function DragonKnight\page_title;
use function DragonKnight\redirect;
use function DragonKnight\render;
use function DragonKnight\user;
class Towns class Towns
{ {
public static function register_routes(Router $r): Router public static function register_routes(Router $r): Router
@ -36,7 +46,7 @@ class Towns
*/ */
public static function town() public static function town()
{ {
$town = Lib::get_town_by_xy(Lib::user()->longitude, Lib::user()->latitude); $town = get_town_by_xy(user()->longitude, user()->latitude);
if ($town === false) { if ($town === false) {
exit('There is an error with your user account, or with the town data. Please try again.'); exit('There is an error with your user account, or with the town data. Please try again.');
} }
@ -44,9 +54,9 @@ class Towns
$page = ['news' => '', 'whos_online' => '']; $page = ['news' => '', 'whos_online' => ''];
// News box. Grab latest news entry and display it. Something a little more graceful coming soon maybe. // News box. Grab latest news entry and display it. Something a little more graceful coming soon maybe.
if (Lib::env('show_news')) { if (env('show_news')) {
$news = Lib::db()->query('SELECT * FROM news ORDER BY id DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC); $news = db()->query('SELECT * FROM news ORDER BY id DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC);
$news_date = Lib::pretty_date($news['postdate']); $news_date = pretty_date($news['postdate']);
$news_content = nl2br($news['content']); $news_content = nl2br($news['content']);
$page['news'] = <<<HTML $page['news'] = <<<HTML
<div class="title">Latest News</div> <div class="title">Latest News</div>
@ -56,8 +66,8 @@ class Towns
} }
// Who's Online. Currently just members. Guests maybe later. // Who's Online. Currently just members. Guests maybe later.
if (Lib::env('show_online')) { if (env('show_online')) {
$onlinequery = Lib::db()->query(<<<SQL $onlinequery = db()->query(<<<SQL
SELECT id, username SELECT id, username
FROM users FROM users
WHERE onlinetime >= datetime('now', '-600 seconds') WHERE onlinetime >= datetime('now', '-600 seconds')
@ -75,13 +85,13 @@ class Towns
$online_rows = implode(', ', $online_rows); $online_rows = implode(', ', $online_rows);
$page['whos_online'] = <<<HTML $page['whos_online'] = <<<HTML
<div class="title">Who's Online</div> <div class="title">Who's Online</div>
There are <b>$online_count</b> Lib::user(s) online within the last 10 minutes: $online_rows There are <b>$online_count</b> user(s) online within the last 10 minutes: $online_rows
HTML; HTML;
} }
Lib::page_title($town['name']); page_title($town['name']);
return Lib::render('towns', ['town' => $town, 'news' => $page['news'], 'whos_online' => $page['whos_online']]); return render('towns', ['town' => $town, 'news' => $page['news'], 'whos_online' => $page['whos_online']]);
} }
/** /**
@ -90,25 +100,25 @@ class Towns
*/ */
public static function inn() public static function inn()
{ {
$town = Lib::get_town_by_xy(Lib::user()->longitude, Lib::user()->latitude); $town = get_town_by_xy(user()->longitude, user()->latitude);
if ($town === false) { if ($town === false) {
exit('Cheat attempt detected.<br><br>Get a life, loser.'); exit('Cheat attempt detected.<br><br>Get a life, loser.');
} }
if (Lib::user()->gold < $town['innprice']) { if (user()->gold < $town['innprice']) {
$page = <<<HTML $page = <<<HTML
You do not have enough gold to stay at this Inn tonight. <br><br> You do not have enough gold to stay at this Inn tonight. <br><br>
You may return to <a hx-get="/" hx-target="#middle">town</a>, or use the direction buttons on the left to start exploring. You may return to <a hx-get="/" hx-target="#middle">town</a>, or use the direction buttons on the left to start exploring.
HTML; HTML;
} elseif (Lib::is_post() && $_POST['rest']) { } elseif (is_post() && $_POST['rest']) {
Lib::user()->gold -= $town['innprice']; user()->gold -= $town['innprice'];
Lib::user()->restore_points()->save(); user()->restore_points()->save();
$page = <<<HTML $page = <<<HTML
You wake up feeling refreshed and ready for action. <br><br> You wake up feeling refreshed and ready for action. <br><br>
You may return to <a hx-get="/" hx-target="#middle">town</a>, or use the direction buttons on the left to start exploring. You may return to <a hx-get="/" hx-target="#middle">town</a>, or use the direction buttons on the left to start exploring.
HTML; HTML;
} elseif (Lib::is_post() && ! $_POST['rest']) { } elseif (is_post() && ! $_POST['rest']) {
Lib::redirect('/'); redirect('/');
} else { } else {
$page = <<<HTML $page = <<<HTML
Resting at the inn will refill your current HP, MP, and TP to their maximum levels.<br><br> Resting at the inn will refill your current HP, MP, and TP to their maximum levels.<br><br>
@ -120,7 +130,7 @@ class Towns
HTML; HTML;
} }
Lib::page_title($town['name'].' Inn'); page_title($town['name'].' Inn');
return $page; return $page;
} }
@ -132,7 +142,7 @@ class Towns
*/ */
public static function shop() public static function shop()
{ {
$town = Lib::get_town_by_xy(Lib::user()->longitude, Lib::user()->latitude); $town = get_town_by_xy(user()->longitude, user()->latitude);
if ($town === false) { if ($town === false) {
exit('Cheat attempt detected.<br><br>Get a life, loser.'); exit('Cheat attempt detected.<br><br>Get a life, loser.');
} }
@ -144,7 +154,7 @@ class Towns
<table> <table>
HTML; HTML;
$items = Lib::db()->query('SELECT * FROM items WHERE id IN ('.$town['itemslist'].');'); $items = db()->query('SELECT * FROM items WHERE id IN ('.$town['itemslist'].');');
while ($item = $items->fetchArray(SQLITE3_ASSOC)) { while ($item = $items->fetchArray(SQLITE3_ASSOC)) {
$attrib = ($item['type'] == 1) ? 'Attack Power:' : 'Defense Power:'; $attrib = ($item['type'] == 1) ? 'Attack Power:' : 'Defense Power:';
$page .= '<tr><td width="4%">'; $page .= '<tr><td width="4%">';
@ -154,7 +164,7 @@ class Towns
3 => '<img src="/img/icon_shield.gif" alt="shield">' 3 => '<img src="/img/icon_shield.gif" alt="shield">'
}; };
$page .= '</td>'; $page .= '</td>';
if (Lib::user()->weaponid === $item['id'] || Lib::user()->armorid === $item['id'] || Lib::user()->shieldid === $item['id']) { if (user()->weaponid === $item['id'] || user()->armorid === $item['id'] || user()->shieldid === $item['id']) {
$page .= <<<HTML $page .= <<<HTML
<td width="32%"><span class="light">{$item['name']}</span></td> <td width="32%"><span class="light">{$item['name']}</span></td>
<td width="32%"><span class="light">$attrib {$item['attribute']}</span></td> <td width="32%"><span class="light">$attrib {$item['attribute']}</span></td>
@ -175,7 +185,7 @@ class Towns
If you've changed your mind, you may also return back to <a hx-get="/" hx-target="#middle">town</a>. If you've changed your mind, you may also return back to <a hx-get="/" hx-target="#middle">town</a>.
HTML; HTML;
Lib::page_title($town['name'].' Shop'); page_title($town['name'].' Shop');
return $page; return $page;
} }
@ -185,15 +195,15 @@ class Towns
*/ */
public static function buy(int $id) public static function buy(int $id)
{ {
$town = Lib::get_town_by_xy(Lib::user()->longitude, Lib::user()->latitude); $town = get_town_by_xy(user()->longitude, user()->latitude);
if ($town === false) { if ($town === false) {
Lib::redirect('/'); redirect('/');
} }
if (! in_array($id, explode(',', $town['itemslist']))) { if (! in_array($id, explode(',', $town['itemslist']))) {
Lib::redirect('/shop'); redirect('/shop');
} }
$item = Lib::get_item($id); $item = get_item($id);
$can_afford = Lib::user()->gold >= $item['buycost']; $can_afford = user()->gold >= $item['buycost'];
if (! $can_afford) { if (! $can_afford) {
$page = <<<HTML $page = <<<HTML
@ -201,9 +211,9 @@ class Towns
You may return to <a hx-get="/" hx-target="#middle">town</a>, <a hx-get="/shop" hx-target="#middle">shop</a>, You may return to <a hx-get="/" hx-target="#middle">town</a>, <a hx-get="/shop" hx-target="#middle">shop</a>,
or use the direction buttons on the left to start exploring. or use the direction buttons on the left to start exploring.
HTML; HTML;
} elseif (Lib::is_post() && ! $_POST['buy']) { } elseif (is_post() && ! $_POST['buy']) {
Lib::redirect('/shop'); redirect('/shop');
} elseif (Lib::is_post() && $_POST['buy']) { } elseif (is_post() && $_POST['buy']) {
$type_mapping = [ $type_mapping = [
1 => ['id' => 'weaponid', 'name' => 'weaponname', 'power' => 'attackpower'], 1 => ['id' => 'weaponid', 'name' => 'weaponname', 'power' => 'attackpower'],
2 => ['id' => 'armorid', 'name' => 'armorname', 'power' => 'defensepower'], 2 => ['id' => 'armorid', 'name' => 'armorname', 'power' => 'defensepower'],
@ -215,9 +225,9 @@ class Towns
} }
// Retrieve current equipped item or create a default // Retrieve current equipped item or create a default
$current_equip_id = Lib::user()->{$type_mapping[$item['type']]['id']}; $current_equip_id = user()->{$type_mapping[$item['type']]['id']};
if ($current_equip_id != 0) { if ($current_equip_id != 0) {
$item2 = Lib::get_item($current_equip_id); $item2 = get_item($current_equip_id);
} else { } else {
$item2 = ['attribute' => 0, 'buycost' => 0, 'special' => 'X']; $item2 = ['attribute' => 0, 'buycost' => 0, 'special' => 'X'];
} }
@ -233,9 +243,9 @@ class Towns
$toChange = $special[0]; $toChange = $special[0];
$changeAmount = $index === 0 ? $special[1] : -$special[1]; $changeAmount = $index === 0 ? $special[1] : -$special[1];
Lib::user()->$toChange += $changeAmount; user()->$toChange += $changeAmount;
$specialFields[] = "$toChange = ?"; $specialFields[] = "$toChange = ?";
$specialValues[] = Lib::user()->$toChange; $specialValues[] = user()->$toChange;
// Adjust attack or defense power // Adjust attack or defense power
if ($toChange == 'strength' || $toChange == 'dexterity') { if ($toChange == 'strength' || $toChange == 'dexterity') {
@ -248,21 +258,21 @@ class Towns
// Determine power and type-specific updates // Determine power and type-specific updates
$currentType = $type_mapping[$item['type']]; $currentType = $type_mapping[$item['type']];
$powerField = $currentType['power']; $powerField = $currentType['power'];
Lib::user()->$powerField += $item['attribute'] - $item2['attribute']; user()->$powerField += $item['attribute'] - $item2['attribute'];
// Calculate new gold with trade-in value // Calculate new gold with trade-in value
Lib::user()->gold += ceil($item2['buycost'] / 2) - $item['buycost']; user()->gold += ceil($item2['buycost'] / 2) - $item['buycost'];
// Ensure current HP/MP/TP don't exceed max values // Ensure current HP/MP/TP don't exceed max values
Lib::user()->currenthp = min(Lib::user()->currenthp, Lib::user()->maxhp); user()->currenthp = min(user()->currenthp, user()->maxhp);
Lib::user()->currentmp = min(Lib::user()->currentmp, Lib::user()->maxmp); user()->currentmp = min(user()->currentmp, user()->maxmp);
Lib::user()->currenttp = min(Lib::user()->currenttp, Lib::user()->maxtp); user()->currenttp = min(user()->currenttp, user()->maxtp);
// Update item info in user // Update item info in user
Lib::user()->{$type_mapping[$item['type']]['id']} = $item['id']; user()->{$type_mapping[$item['type']]['id']} = $item['id'];
Lib::user()->{$type_mapping[$item['type']]['name']} = $item['name']; user()->{$type_mapping[$item['type']]['name']} = $item['name'];
Lib::user()->save(); user()->save();
$page = <<<HTML $page = <<<HTML
Thank you for purchasing <b>{$item['name']}</b>.<br><br> Thank you for purchasing <b>{$item['name']}</b>.<br><br>
@ -271,10 +281,10 @@ class Towns
HTML; HTML;
} else { } else {
$type_to_row_mapping = [1 => 'weaponid', 2 => 'armorid', 3 => 'shieldid']; $type_to_row_mapping = [1 => 'weaponid', 2 => 'armorid', 3 => 'shieldid'];
$current_equipped_id = Lib::user()->{$type_to_row_mapping[$item['type']]} ?? 0; $current_equipped_id = user()->{$type_to_row_mapping[$item['type']]} ?? 0;
if ($current_equipped_id != 0) { if ($current_equipped_id != 0) {
$item2 = Lib::get_item($current_equipped_id); $item2 = get_item($current_equipped_id);
$sell_price = ceil($item2['buycost'] / 2); $sell_price = ceil($item2['buycost'] / 2);
$page = <<<HTML $page = <<<HTML
If you are buying the {$item['name']}, then I will buy your {$item2['name']} for $sell_price gold. Is that ok?<br><br> If you are buying the {$item['name']}, then I will buy your {$item2['name']} for $sell_price gold. Is that ok?<br><br>
@ -294,7 +304,7 @@ class Towns
} }
} }
Lib::page_title('Buying '.$item['name']); page_title('Buying '.$item['name']);
return $page; return $page;
} }
@ -310,8 +320,8 @@ class Towns
<table> <table>
HTML; HTML;
$mapped = explode(',', Lib::user()->towns); $mapped = explode(',', user()->towns);
$towns = Lib::db()->query('SELECT * FROM towns ORDER BY id;'); $towns = db()->query('SELECT * FROM towns ORDER BY id;');
while ($town = $towns->fetchArray(SQLITE3_ASSOC)) { while ($town = $towns->fetchArray(SQLITE3_ASSOC)) {
$latitude = ($town['latitude'] >= 0) ? $town['latitude'].'N,' : ($town['latitude'] * -1).'S,'; $latitude = ($town['latitude'] >= 0) ? $town['latitude'].'N,' : ($town['latitude'] * -1).'S,';
$longitude = ($town['longitude'] >= 0) ? $town['longitude'].'E' : ($town['longitude'] * -1).'W'; $longitude = ($town['longitude'] >= 0) ? $town['longitude'].'E' : ($town['longitude'] * -1).'W';
@ -341,34 +351,34 @@ class Towns
If you've changed your mind, you may also return back to <a hx-get="/" hx-target="#middle">town</a>. If you've changed your mind, you may also return back to <a hx-get="/" hx-target="#middle">town</a>.
HTML; HTML;
Lib::page_title('Maps'); page_title('Maps');
return $page; return $page;
} }
public static function buy_map(int $id): string public static function buy_map(int $id): string
{ {
$town = Lib::get_town_by_id($id); $town = get_town_by_id($id);
if ($town === false) { if ($town === false) {
Lib::redirect('/maps'); redirect('/maps');
} }
if (Lib::user()->gold < $town['mapprice']) { if (user()->gold < $town['mapprice']) {
$page = <<<HTML $page = <<<HTML
You do not have enough gold to buy this map.<br><br> You do not have enough gold to buy this map.<br><br>
You may return to <a hx-get="/" hx-target="#middle">town</a>, <a hx-get="/maps" hx-target="#middle">store</a>, or use the direction buttons on the left to start exploring. You may return to <a hx-get="/" hx-target="#middle">town</a>, <a hx-get="/maps" hx-target="#middle">store</a>, or use the direction buttons on the left to start exploring.
HTML; HTML;
} elseif (Lib::is_post() && $_POST['buy']) { } elseif (is_post() && $_POST['buy']) {
Lib::user()->towns .= ",$id"; user()->towns .= ",$id";
Lib::user()->gold -= $town['mapprice']; user()->gold -= $town['mapprice'];
Lib::user()->save(); user()->save();
$page = <<<HTML $page = <<<HTML
Thank you for purchasing this map.<br><br> Thank you for purchasing this map.<br><br>
You may return to <a hx-get="/" hx-target="#middle">town</a>, <a hx-get="/maps" hx-target="#middle">map shop</a>, or use the direction buttons on the left to start exploring. You may return to <a hx-get="/" hx-target="#middle">town</a>, <a hx-get="/maps" hx-target="#middle">map shop</a>, or use the direction buttons on the left to start exploring.
HTML; HTML;
} elseif (Lib::is_post() && ! $_POST['buy']) { } elseif (is_post() && ! $_POST['buy']) {
Lib::redirect('/maps'); redirect('/maps');
} else { } else {
$page = <<<HTML $page = <<<HTML
You are buying the <b>{$town['name']}</b> map for {$town['mapprice']} gold. Is that ok?<br><br> You are buying the <b>{$town['name']}</b> map for {$town['mapprice']} gold. Is that ok?<br><br>
@ -379,7 +389,7 @@ class Towns
HTML; HTML;
} }
Lib::page_title('Buying '.$town['name'].' Map'); page_title('Buying '.$town['name'].' Map');
return $page; return $page;
} }
@ -389,24 +399,24 @@ class Towns
*/ */
public static function travelto(int $id, bool $use_points = true): string public static function travelto(int $id, bool $use_points = true): string
{ {
if (Lib::user()->currentaction == 'Fighting') { if (user()->currentaction == 'Fighting') {
Lib::redirect('/fight'); redirect('/fight');
} }
$town = Lib::get_town_by_id($id); $town = get_town_by_id($id);
$cost = $use_points ? $town['travelpoints'] : 0; $cost = $use_points ? $town['travelpoints'] : 0;
$mapped = explode(',', Lib::user()->towns); $mapped = explode(',', user()->towns);
$travelled = false; $travelled = false;
if ($use_points && ! in_array($id, $mapped)) { if ($use_points && ! in_array($id, $mapped)) {
// trying to teleport to this town when it is not mapped // trying to teleport to this town when it is not mapped
Lib::redirect('/'); redirect('/');
} elseif (Lib::user()->currenttp < $cost) { } elseif (user()->currenttp < $cost) {
$page = 'You do not have enough TP to travel here. Please <a href="/" hx-get="/" hx-target="#middle">go back</a> and try again when you get more TP.'; $page = 'You do not have enough TP to travel here. Please <a href="/" hx-get="/" hx-target="#middle">go back</a> and try again when you get more TP.';
} elseif ((Lib::user()->latitude == $town['latitude']) && (Lib::user()->longitude == $town['longitude'])) { } elseif ((user()->latitude == $town['latitude']) && (user()->longitude == $town['longitude'])) {
if (! in_array($id, $mapped)) { if (! in_array($id, $mapped)) {
// add town to user's mapped if they travelled here // add town to user's mapped if they travelled here
Lib::user()->towns .= ",$id"; user()->towns .= ",$id";
$travelled = true; $travelled = true;
$page = <<<HTML $page = <<<HTML
You have discovered <b>{$town['name']}</b>! It has been added to your mapped towns.<br><br> You have discovered <b>{$town['name']}</b>! It has been added to your mapped towns.<br><br>
@ -416,19 +426,19 @@ class Towns
$page = 'You are already in this town. <a href="/" hx-get="/" hx-target="#middle">Click here</a> to return.'; $page = 'You are already in this town. <a href="/" hx-get="/" hx-target="#middle">Click here</a> to return.';
} }
} else { } else {
Lib::user()->latitude = $town['latitude']; user()->latitude = $town['latitude'];
Lib::user()->longitude = $town['longitude']; user()->longitude = $town['longitude'];
Lib::user()->currenttp -= $cost; user()->currenttp -= $cost;
$travelled = true; $travelled = true;
$page = 'You have travelled to <b>'.$town['name'].'</b>. You may now <a href="/" hx-get="/" hx-target="#middle">enter this town</a>.'; $page = 'You have travelled to <b>'.$town['name'].'</b>. You may now <a href="/" hx-get="/" hx-target="#middle">enter this town</a>.';
} }
if ($travelled) { if ($travelled) {
Lib::user()->currentaction = 'In Town'; user()->currentaction = 'In Town';
Lib::user()->save(); user()->save();
} }
Lib::page_title('Travelling to '.$town['name']); page_title('Travelling to '.$town['name']);
return $page; return $page;
} }

View File

@ -66,7 +66,7 @@ class Auth
public function login(string $username, string $password): bool public function login(string $username, string $password): bool
{ {
$user = Lib::get_user($username); $user = get_user($username);
if ($user === false) { if ($user === false) {
return false; return false;
} }

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/*
* This file is a part of the Dragon-Knight project.
*
* Copyright (c) 2024-present Sharkk
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE.md file.
*/
namespace DragonKnight;
define('VERSION', '1.2.5');
define('BUILD', 'Reawaken');
define('START', microtime(true));

View File

@ -1,731 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is a part of the Dragon-Knight project.
*
* Copyright (c) 2024-present Sharkk
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE.md file.
*/
namespace DragonKnight;
use DragonKnight\Actions\Explore;
use DragonKnight\Actions\Towns;
use DragonKnight\Models\User;
define('VERSION', '1.2.5');
define('BUILD', 'Reawaken');
define('START', microtime(true));
class Lib
{
/**
* Return a page for a couple generic actions.
*/
public static function index(): string
{
if (self::user()->currentaction === 'In Town') {
$page = Towns::town();
} elseif (self::user()->currentaction === 'Exploring') {
$page = Explore::explore();
} elseif (self::user()->currentaction === 'Fighting') {
self::redirect('/fight');
}
return $page;
}
/**
* Show the user their position on the current world map. Only works with a game size of 250 and the default towns 😅.
*/
public static function show_map()
{
$pos = sprintf(
'<div style="position: absolute; width: 5px; height: 5px; border-radius: 1000px; border: solid 1px black; background-color: red; left: %dpx; top: %dpx;"></div>',
round(258 + Lib::user()->longitude * (500 / 500) - 3),
round(258 - Lib::user()->latitude * (500 / 500) - 3)
);
echo Lib::render('layouts/minimal', [
'content' => '<img src="/img/map.gif" alt="Map">'.$pos,
'title' => 'Map',
]);
}
/**
* Show a character's info. Defaults to the currently logged in user.
*/
public static function show_character_info(int $id = 0): string
{
$user = $id !== 0 ? User::find($id) : Lib::user();
if ($user === false) {
exit('Failed to show info for user ID '.$id);
}
$level = Lib::db()->query("SELECT `{$user->charclass}_exp` FROM levels WHERE id=? LIMIT 1;", [$user->level + 1])->fetchArray(SQLITE3_ASSOC);
$spells = $user->spells();
$magic_list = 'None';
if (! empty($spells)) {
$magic_list = '';
foreach ($spells as $spell) {
$magic_list .= $spell['name'].'<br>';
}
}
$showchar = Lib::render('show_char', [
'char' => $user,
'level' => $level,
'magic_list' => $magic_list,
]);
return Lib::render('layouts/minimal', ['content' => $showchar, 'title' => $user->username.' Information']);
}
/**
* Handle a POST request to send a new babblebox message.
*/
public static function babblebox()
{
if (Lib::is_post()) {
$content = trim($_POST['babble']);
if (! empty($content)) {
Lib::db()->query(
'INSERT INTO babble (posttime, author, babble) VALUES (CURRENT_TIMESTAMP, ?, ?);',
[Lib::user()->username, $content]
);
}
return self::babblebox_messages();
}
}
/**
* The handler that is polled by HTMX for new babblebox messages.
*/
public static function babblebox_messages(): string
{
if (Lib::user() === false) {
return '';
}
$query = Lib::db()->query('SELECT * FROM babble ORDER BY id ASC LIMIT 40;');
$has_chats = false;
$messages = '';
while ($row = $query->fetchArray(SQLITE3_ASSOC)) {
$has_chats = true;
$messages .= '<div class="message">[<b>'.$row['author'].'</b>] '.Lib::make_safe($row['babble']).'</div>';
}
if (! $has_chats) {
$messages = 'There are no messages. :(';
}
return $messages;
}
/**
* Open or get SQLite database connection.
*/
public static function db(): Database
{
if (! is_dir($path = getcwd().'/db')) {
error_log('Database folder not found at '.$path.'. Please run the installer first.');
exit();
}
return $GLOBALS['database'] ??= new Database(getcwd().'/db/database.db');
}
/**
* Redirect to a different URL, exit.
*/
public static function redirect(string $location): void
{
if (self::is_htmx()) {
$target = isset($_SERVER['HTTP_HX_TARGET']) ? '#'.$_SERVER['HTTP_HX_TARGET'] : '#middle';
$json = json_encode(['path' => $location, 'target' => $target]);
header("HX-Location: $json");
} else {
header("Location: $location");
}
exit;
}
/**
* Render a view with the given data. Can be used redundantly within the template.
*/
public static function render(string $path_to_base_view, array $data = []): string|false
{
ob_start();
extract($data);
require __DIR__."/../templates/$path_to_base_view.php";
return ob_get_clean();
}
/**
* Replace tags with given content.
*/
public static function parse(string $template, array $array): string
{
return strtr($template, array_combine(
array_map(fn ($key) => "{{{$key}}}", array_keys($array)),
array_values($array)
));
}
/**
* Change the SQLite3 datetime format (YYYY-MM-DD HH:MM:SS) into something friendlier.
*/
public static function pretty_date(string $uglydate): string
{
return date('l, F j, Y', mktime(
0,
0,
0,
(int) substr($uglydate, 5, 2), // Month
(int) substr($uglydate, 8, 2), // Day
(int) substr($uglydate, 0, 4) // Year
));
}
/**
* Use htmlentities with UTF-8 encoding to ensure we're only outputting healthy, safe and effective HTML.
*/
public static function make_safe(string $content): string
{
return htmlentities($content, ENT_QUOTES, 'UTF-8');
}
/**
* Finalize admin page and output to browser.
*/
public static function display_admin($content, $title)
{
echo self::render('layouts/admin', [
'title' => $title,
'content' => $content,
]);
exit;
}
/**
* Determine what game skin to use. If a user is logged in then it uses their setting, otherwise defaults to 0 (retro).
*/
public static function game_skin(): int
{
return self::user() !== false ? self::user()->game_skin : 0;
}
/**
* Get a town's data by it's coordinates.
*/
public static function get_town_by_xy(int $x, int $y): array|false
{
$cache_tag = "town-$x-$y";
if (! isset($GLOBALS['cache'][$cache_tag])) {
$query = self::db()->query('SELECT * FROM towns WHERE longitude = ? AND latitude = ? LIMIT 1;', [$x, $y]);
if ($query === false) {
return false;
}
$GLOBALS['cache'][$cache_tag] = $query->fetchArray(SQLITE3_ASSOC);
}
return $GLOBALS['cache'][$cache_tag];
}
/**
* Get a town's data by it's ID.
*/
public static function get_town_by_id(int $id): array|false
{
$query = self::db()->query('SELECT * FROM towns WHERE id = ? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get a user's data by their ID, username or email.
*/
public static function get_user(int|string $id, string $data = '*'): array|false
{
$query = self::db()->query(
"SELECT $data FROM users WHERE id=? OR username=? COLLATE NOCASE OR email=? COLLATE NOCASE LIMIT 1;",
[$id, $id, $id]
);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get an item by it's ID.
*/
public static function get_item(int $id): array|false
{
$query = self::db()->query('SELECT * FROM items WHERE id=? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get a drop by it's ID.
*/
public static function get_drop(int $id): array|false
{
$query = self::db()->query('SELECT * FROM drops WHERE id=? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get a spell by it's ID.
*/
public static function get_spell(int $id): array|false
{
$query = self::db()->query('SELECT * FROM spells WHERE id=? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get a monster by it's ID.
*/
public static function get_monster(int $id): array|false
{
$query = self::db()->query('SELECT * FROM monsters WHERE id=? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Translate a Specials keyword to it's string.
*/
public static function special_to_string(string $special): string
{
return match ($special) {
'maxhp' => 'Max HP',
'maxmp' => 'Max MP',
'maxtp' => 'Max TP',
'goldbonus' => 'Gold Bonus (%)',
'expbonus' => 'Experience Bonus (%)',
'strength' => 'Strength',
'dexterity' => 'Dexterity',
'attackpower' => 'Attack Power',
'defensepower' => 'Defense Power',
default => $special
};
}
/**
* Generate a pretty dope token.
*/
public static function token($length = 32): string
{
return bin2hex(random_bytes($length));
}
/**
* Validate any given array of data against rules. Returns [valid, data, error]. Data contains the trimmed
* values from the input array. Note: all fields with rules are assumed to be required, unless the optional
* rule is used.
*
* Example: ['required', 'no-trim', 'length:5-20', 'alphanum-spaces']
*/
public static function validate(array $input_data, array $rules): array
{
$data = [];
$errors = [];
foreach ($rules as $field => $field_rules) {
$value = $input_data[$field] ?? null;
$field_name = ucfirst(str_replace('_', ' ', $field));
$is_required = true;
$default_value = null;
if (in_array('optional', $field_rules)) {
$is_required = false;
}
foreach ($field_rules as $rule) {
if (strpos($rule, 'default:') === 0) {
$default_value = substr($rule, 8);
break;
}
}
if (($value === null || $value === '') && $default_value !== null) {
$value = $default_value;
}
if (($value === null || $value === '') && ! $is_required) {
continue;
}
if ($is_required && ($value === null || $value === '')) {
$errors[$field][] = "{$field_name} is required.";
continue;
}
if (! in_array('no-trim', $field_rules)) {
$value = trim($value);
}
$data[$field] = $value;
foreach ($field_rules as $rule) {
// Parse rule and arguments
if (strpos($rule, ':') !== false) {
list($rule_name, $rule_args) = explode(':', $rule, 2);
} else {
$rule_name = $rule;
$rule_args = null;
}
if ($rule_name === 'optional') {
continue;
}
switch ($rule_name) {
case 'bool':
if (! isset($input_data[$field]) || empty($value)) {
$value = false;
} else {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($value === null) {
$errors[$field][] = "{$field_name} must be a valid boolean value.";
}
}
break;
case 'length':
list($min, $max) = explode('-', $rule_args);
$len = strlen((string) $value);
if ($len < $min || $len > $max) {
$errors[$field][] = "{$field_name} must be between {$min} and {$max} characters.";
}
break;
case 'alphanum':
if (! preg_match('/^[a-zA-Z0-9]+$/', $value)) {
$errors[$field][] = "{$field_name} must contain only letters and numbers.";
}
break;
case 'alpha':
if (! preg_match('/^[a-zA-Z]+$/', $value)) {
$errors[$field][] = "{$field_name} must contain only letters and numbers.";
}
break;
case 'alphanum-spaces':
if (! preg_match('/^[a-zA-Z0-9\s_]+$/', $value)) {
$errors[$field][] = "{$field_name} must contain only letters, numbers, spaces, and underscores.";
}
break;
case 'alpha-spaces':
if (! preg_match('/^[a-zA-Z\s_]+$/', $value)) {
$errors[$field][] = "{$field_name} must contain only letters, numbers, spaces, and underscores.";
}
break;
case 'email':
if (! filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$field][] = "{$field_name} must be a valid email address.";
}
break;
case 'int':
if (filter_var($value, FILTER_VALIDATE_INT) === false) {
$errors[$field][] = "{$field_name} must be an integer.";
}
break;
case 'min':
if ($value < $rule_args) {
$errors[$field][] = "{$field_name} must be at least {$rule_args}.";
}
break;
case 'max':
if ($value > $rule_args) {
$errors[$field][] = "{$field_name} must be no more than {$rule_args}.";
}
break;
case 'regex':
if (! preg_match($rule_args, $value)) {
$errors[$field][] = "{$field_name} does not match the required pattern.";
}
break;
case 'in':
$options = explode(',', $rule_args);
if (! in_array($value, $options)) {
$errors[$field][] = "{$field_name} must be one of: ".implode(', ', $options);
}
break;
case 'confirm':
$field_to_confirm = substr($field, 8);
$confirm_value = $data[$field_to_confirm] ?? '';
$confirm_field_name = ucfirst(str_replace('_', ' ', $field_to_confirm));
if ($value !== $confirm_value) {
$errors[$field][] = "{$field_name} must match {$confirm_field_name}.";
}
break;
case 'unique':
list($table, $column) = explode(',', $rule_args, 2);
if (self::db()->exists($table, $column, $value)) {
$errors[$field][] = "{$field_name} must be unique.";
}
break;
}
}
}
foreach ($input_data as $field => $value) {
if (! isset($data[$field])) {
$data[$field] = trim($value);
}
}
return [
'valid' => empty($errors),
'data' => $data,
'errors' => $errors,
];
}
/**
* Generates a ul list from `validate()`'s errors.
*/
public static function ul_from_validate_errors(array $errors): string
{
$string = '<ul>';
foreach ($errors as $field => $errors) {
$string .= '<li>';
foreach ($errors as $error) {
$string .= $error;
}
$string .= '</li>';
}
return $string.'</ul>';
}
/**
* Load the environment variables from the .env file.
*/
public static function env_load(string $filePath): void
{
if (! file_exists($filePath)) {
throw new \Exception('The .env file does not exist. (el)');
}
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
// Skip lines that are empty after trimming or are comments
if ($line === '' || str_starts_with($line, '#')) {
continue;
}
// Skip lines without an '=' character
if (strpos($line, '=') === false) {
continue;
}
[$name, $value] = explode('=', $line, 2);
$name = trim($name);
$value = trim($value, " \t\n\r\0\x0B\"'"); // Trim whitespace and quotes
if (! array_key_exists($name, $_SERVER) && ! array_key_exists($name, $_ENV)) {
putenv("$name=$value");
$_ENV[$name] = $value;
$_SERVER[$name] = $value;
}
}
}
/**
* Retrieve an environment variable.
*/
public static function env(string $key, mixed $default = null): mixed
{
$v = $_ENV[$key] ?? $_SERVER[$key] ?? (getenv($key) ?: $default);
return match (true) {
$v === 'true' => true,
$v === 'false' => false,
is_numeric($v) => (int) $v,
is_float($v) => (float) $v,
default => $v
};
}
/**
* Get the data on spells from a given list of IDs.
*/
public static function get_spells_from_list(array|string $spell_ids): array|false
{
if (is_string($spell_ids)) {
$spell_ids = explode(',', $spell_ids);
}
$placeholders = implode(',', array_fill(0, count($spell_ids), '?'));
$query = self::db()->query("SELECT id, name, type FROM spells WHERE id IN($placeholders)", $spell_ids);
if ($query === false) {
return false;
}
$rows = [];
while ($row = $query->fetchArray(SQLITE3_ASSOC)) {
$rows[] = $row;
}
return ! empty($rows) ? $rows : false;
}
public static function generate_stat_bar(int $current, int $max): string
{
$percent = $max > 0 ? round(max(0, $current) / $max * 100, 4) : 0;
if ($percent < 0) {
$percent = 0;
}
if ($percent > 100) {
$percent = 100;
}
$color = $percent >= 66 ? 'green' : ($percent >= 33 ? 'yellow' : 'red');
return <<<HTML
<div class="stat-bar" style="width: 15px; height: 100px; border: solid 1px black;">
<div style="height: $percent%; background-image: url(/img/bars_$color.gif);"></div>
</div>
HTML;
}
public static function create_stat_table(): string
{
$stat_table = '<div class="stat-table">'.
'<div class="stat-row">'.
'<div class="stat-col">'.self::generate_stat_bar((int) self::user()->currenthp, (int) self::user()->maxhp).'<div>HP</div></div>'.
'<div class="stat-col">'.self::generate_stat_bar((int) self::user()->currentmp, (int) self::user()->maxmp).'<div>MP</div></div>'.
'<div class="stat-col">'.self::generate_stat_bar((int) self::user()->currenttp, (int) self::user()->maxtp).'<div>TP</div></div>'.
'</div>'.
'</div>';
return $stat_table;
}
/**
* Returns the user in the GLOBALS state, if there is one. If not, populates it if there is a SESSION user_id.
*/
public static function user(): User|false
{
$GLOBALS['state']['user'] ??= (isset($_SESSION['user_id']) ? User::find($_SESSION['user_id']) : false);
return $GLOBALS['state']['user'];
}
/**
* Determine whether a request is from HTMX. If HTMX is trying to restore history, we will say no in order to render
* full pages.
*/
public static function is_htmx(): bool
{
if (isset($_SERVER['HTTP_HX_HISTORY_RESTORE_REQUEST']) && $_SERVER['HTTP_HX_HISTORY_RESTORE_REQUEST'] === 'true') {
return false;
}
return isset($_SERVER['HTTP_HX_REQUEST']) && $_SERVER['HTTP_HX_REQUEST'] === 'true';
}
/**
* Return whether the request is POST.
*/
public static function is_post(): bool
{
return $_SERVER['REQUEST_METHOD'] === 'POST';
}
/**
* Get the current page title per updates. Optionally set a new title.
*/
public static function page_title(string $new_title = ''): string
{
if ($new_title) {
return $GLOBALS['state']['new-page-title'] = $new_title;
}
return $GLOBALS['state']['new-page-title'] ?? self::env('game_name');
}
/**
* Render the response for the browser based on the request context. The main point is to seperate the handling
* of HTMX responses from normal responses.
*/
public static function render_response(array $uri, string $content): string
{
if ($uri[0] === 'babblebox') {
return $content;
}
if (self::is_htmx()) {
header('HX-Push-Url: '.$_SERVER['REQUEST_URI']);
$content .= '<title>'.self::page_title().'</title>';
$content .= Render::debug_db_info();
if (self::env('debug', false)) {
$content .= Render::debug_query_log();
}
if ($GLOBALS['state']['user-state-changed'] ?? false) {
$content .= Render::right_nav();
$content .= Render::left_nav();
}
}
return Render::content($content, self::page_layout());
}
/**
* Get/set page layout through GLOBALS state.
*/
public static function page_layout(string $layout = ''): string
{
if ($layout === '') {
return $GLOBALS['state']['page-layout'] ?? 'layouts/primary';
}
return $GLOBALS['state']['page-layout'] = $layout;
}
}

View File

@ -13,8 +13,6 @@ declare(strict_types=1);
namespace DragonKnight\Models; namespace DragonKnight\Models;
use DragonKnight\Lib;
class Model class Model
{ {
protected string $table_name = ''; protected string $table_name = '';
@ -59,7 +57,7 @@ class Model
$values[] = $this->id; $values[] = $this->id;
$query = 'UPDATE '.$this->table_name.' SET '.implode(', ', $placeholders).' WHERE id = ?;'; $query = 'UPDATE '.$this->table_name.' SET '.implode(', ', $placeholders).' WHERE id = ?;';
$result = Lib::db()->query($query, $values); $result = db()->query($query, $values);
return $result === false ? false : true; return $result === false ? false : true;
} }

View File

@ -13,8 +13,6 @@ declare(strict_types=1);
namespace DragonKnight\Models; namespace DragonKnight\Models;
use DragonKnight\Lib;
class User extends Model class User extends Model
{ {
protected string $table_name = 'users'; protected string $table_name = 'users';
@ -24,7 +22,7 @@ class User extends Model
*/ */
public static function find(int|string $id): User|false public static function find(int|string $id): User|false
{ {
$query = Lib::db()->query( $query = db()->query(
'SELECT * FROM users WHERE id=? OR username=? COLLATE NOCASE OR email=? COLLATE NOCASE LIMIT 1;', 'SELECT * FROM users WHERE id=? OR username=? COLLATE NOCASE OR email=? COLLATE NOCASE LIMIT 1;',
[$id, $id, $id] [$id, $id, $id]
); );
@ -44,7 +42,7 @@ class User extends Model
*/ */
public function spells(): array|false public function spells(): array|false
{ {
return Lib::get_spells_from_list($this->spells); return get_spells_from_list($this->spells);
} }
/** /**
@ -67,7 +65,7 @@ class User extends Model
if ($this->onlinetime && strtotime($this->onlinetime) > strtotime('-9 minutes')) { if ($this->onlinetime && strtotime($this->onlinetime) > strtotime('-9 minutes')) {
return; return;
} }
Lib::db()->query('UPDATE users SET onlinetime=CURRENT_TIMESTAMP WHERE id=?;', [$this->id]); db()->query('UPDATE users SET onlinetime=CURRENT_TIMESTAMP WHERE id=?;', [$this->id]);
} }
/** /**
@ -105,7 +103,7 @@ class User extends Model
$values[] = $this->id; $values[] = $this->id;
$query = 'UPDATE '.$this->table_name.' SET '.implode(', ', $placeholders).' WHERE id = ?;'; $query = 'UPDATE '.$this->table_name.' SET '.implode(', ', $placeholders).' WHERE id = ?;';
$result = Lib::db()->query($query, $values); $result = db()->query($query, $values);
if ($result === false) { if ($result === false) {
return false; return false;
} }

View File

@ -21,37 +21,37 @@ class Render
{ {
/** /**
* Prepare content for final render. If the request is HTMX-based, will return just the content passed to it. Otherwise * Prepare content for final render. If the request is HTMX-based, will return just the content passed to it. Otherwise
* it will Lib::render() onto $layout with some additional bits. * it will render() onto $layout with some additional bits.
*/ */
public static function content(string $content, string $layout = 'layouts/primary'): string public static function content(string $content, string $layout = 'layouts/primary'): string
{ {
if (Lib::is_htmx()) { if (is_htmx()) {
return $content; return $content;
} }
return Lib::render($layout, ['content' => $content]); return render($layout, ['content' => $content]);
} }
public static function debug_db_info(): string public static function debug_db_info(): string
{ {
$total_time = round(microtime(true) - START, 4); $total_time = round(microtime(true) - START, 4);
$htmx = Lib::is_htmx() ? ' (htmx)' : ''; $htmx = is_htmx() ? ' (htmx)' : '';
return '<div id="debug-db-info" hx-swap-oob="true">'.$total_time.' Seconds, '.Lib::db()->count.' Queries'.$htmx.'</div>'; return '<div id="debug-db-info" hx-swap-oob="true">'.$total_time.' Seconds, '.db()->count.' Queries'.$htmx.'</div>';
} }
public static function right_nav(): string public static function right_nav(): string
{ {
if (Lib::user() === false) { if (user() === false) {
return ''; return '';
} }
// Flashy numbers if they're low // Flashy numbers if they're low
$hp = (Lib::user()->currenthp <= (Lib::user()->maxhp / 5)) ? '<blink><span class="highlight"><b>*'.Lib::user()->currenthp.'*</b></span></blink>' : Lib::user()->currenthp; $hp = (user()->currenthp <= (user()->maxhp / 5)) ? '<blink><span class="highlight"><b>*'.user()->currenthp.'*</b></span></blink>' : user()->currenthp;
$mp = (Lib::user()->currentmp <= (Lib::user()->maxmp / 5)) ? '<blink><span class="highlight"><b>*'.Lib::user()->currentmp.'*</b></span></blink>' : Lib::user()->currentmp; $mp = (user()->currentmp <= (user()->maxmp / 5)) ? '<blink><span class="highlight"><b>*'.user()->currentmp.'*</b></span></blink>' : user()->currentmp;
$template = Lib::render('right_nav', ['hp' => $hp, 'mp' => $mp]); $template = render('right_nav', ['hp' => $hp, 'mp' => $mp]);
if (Lib::is_htmx()) { if (is_htmx()) {
$template = '<section id="right" hx-swap-oob="true">'.$template.'</section>'; $template = '<section id="right" hx-swap-oob="true">'.$template.'</section>';
} }
@ -60,12 +60,12 @@ class Render
public static function left_nav(): string public static function left_nav(): string
{ {
if (Lib::user() === false) { if (user() === false) {
return ''; return '';
} }
$template = Lib::render('left_nav'); $template = render('left_nav');
if (Lib::is_htmx()) { if (is_htmx()) {
$template = '<section id="left" hx-swap-oob="true">'.$template.'</section>'; $template = '<section id="left" hx-swap-oob="true">'.$template.'</section>';
} }
@ -74,13 +74,13 @@ class Render
public static function babblebox(): string public static function babblebox(): string
{ {
return Lib::render('babblebox', ['messages' => babblebox_messages()]); return render('babblebox', ['messages' => babblebox_messages()]);
} }
public static function debug_query_log(): string public static function debug_query_log(): string
{ {
$html = '<pre id="debug-query-log" hx-swap-oob="true">'; $html = '<pre id="debug-query-log" hx-swap-oob="true">';
foreach (Lib::db()->log as $record) { foreach (db()->log as $record) {
$query_string = str_replace(["\r\n", "\n", "\r"], ' ', $record[0]); $query_string = str_replace(["\r\n", "\n", "\r"], ' ', $record[0]);
$error_string = ! empty($record[2]) ? '// '.$record[2] : ''; $error_string = ! empty($record[2]) ? '// '.$record[2] : '';
$html .= '<div>['.round($record[1], 2)."s] {$query_string}{$error_string}</div>"; $html .= '<div>['.round($record[1], 2)."s] {$query_string}{$error_string}</div>";

View File

@ -0,0 +1,724 @@
<?php
declare(strict_types=1);
/*
* This file is a part of the Dragon-Knight project.
*
* Copyright (c) 2024-present Sharkk
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE.md file.
*/
namespace DragonKnight;
use DragonKnight\Actions\Explore;
use DragonKnight\Actions\Towns;
use DragonKnight\Models\User;
function index(): string|false
{
$page = false;
if (user()->currentaction === 'In Town') {
$page = Towns::town();
} elseif (user()->currentaction === 'Exploring') {
$page = Explore::explore();
} elseif (user()->currentaction === 'Fighting') {
redirect('/fight');
}
return $page;
}
/**
* Show the user their position on the current world map. Only works with a game size of 250 and the default towns 😅.
*/
function show_map()
{
$pos = sprintf(
'<div style="position: absolute; width: 5px; height: 5px; border-radius: 1000px; border: solid 1px black; background-color: red; left: %dpx; top: %dpx;"></div>',
round(258 + user()->longitude * (500 / 500) - 3),
round(258 - user()->latitude * (500 / 500) - 3)
);
echo render('layouts/minimal', [
'content' => '<img src="/img/map.gif" alt="Map">'.$pos,
'title' => 'Map',
]);
}
/**
* Show a character's info. Defaults to the currently logged in user.
*/
function show_character_info(int $id = 0): string
{
$user = $id !== 0 ? User::find($id) : user();
if ($user === false) {
exit('Failed to show info for user ID '.$id);
}
$level = db()->query("SELECT `{$user->charclass}_exp` FROM levels WHERE id=? LIMIT 1;", [$user->level + 1])->fetchArray(SQLITE3_ASSOC);
$spells = $user->spells();
$magic_list = 'None';
if (! empty($spells)) {
$magic_list = '';
foreach ($spells as $spell) {
$magic_list .= $spell['name'].'<br>';
}
}
$showchar = render('show_char', [
'char' => $user,
'level' => $level,
'magic_list' => $magic_list,
]);
return render('layouts/minimal', ['content' => $showchar, 'title' => $user->username.' Information']);
}
/**
* Handle a POST request to send a new babblebox message.
*/
function babblebox()
{
if (is_post()) {
$content = trim($_POST['babble']);
if (! empty($content)) {
db()->query(
'INSERT INTO babble (posttime, author, babble) VALUES (CURRENT_TIMESTAMP, ?, ?);',
[user()->username, $content]
);
}
return babblebox_messages();
}
}
/**
* The handler that is polled by HTMX for new babblebox messages.
*/
function babblebox_messages(): string
{
if (user() === false) {
return '';
}
$query = db()->query('SELECT * FROM babble ORDER BY id ASC LIMIT 40;');
$has_chats = false;
$messages = '';
while ($row = $query->fetchArray(SQLITE3_ASSOC)) {
$has_chats = true;
$messages .= '<div class="message">[<b>'.$row['author'].'</b>] '.make_safe($row['babble']).'</div>';
}
if (! $has_chats) {
$messages = 'There are no messages. :(';
}
return $messages;
}
/**
* Open or get SQLite database connection.
*/
function db(): Database
{
if (! is_dir($path = getcwd().'/db')) {
error_log('Database folder not found at '.$path.'. Please run the installer first.');
exit();
}
return $GLOBALS['database'] ??= new Database(getcwd().'/db/database.db');
}
/**
* Redirect to a different URL, exit.
*/
function redirect(string $location): void
{
if (is_htmx()) {
$target = isset($_SERVER['HTTP_HX_TARGET']) ? '#'.$_SERVER['HTTP_HX_TARGET'] : '#middle';
$json = json_encode(['path' => $location, 'target' => $target]);
header("HX-Location: $json");
} else {
header("Location: $location");
}
exit;
}
/**
* Render a view with the given data. Can be used redundantly within the template.
*/
function render(string $path_to_base_view, array $data = []): string|false
{
ob_start();
extract($data);
require __DIR__."/../templates/$path_to_base_view.php";
return ob_get_clean();
}
/**
* Replace tags with given content.
*/
function parse(string $template, array $array): string
{
return strtr($template, array_combine(
array_map(fn ($key) => "{{{$key}}}", array_keys($array)),
array_values($array)
));
}
/**
* Change the SQLite3 datetime format (YYYY-MM-DD HH:MM:SS) into something friendlier.
*/
function pretty_date(string $uglydate): string
{
return date('l, F j, Y', mktime(
0,
0,
0,
(int) substr($uglydate, 5, 2), // Month
(int) substr($uglydate, 8, 2), // Day
(int) substr($uglydate, 0, 4) // Year
));
}
/**
* Use htmlentities with UTF-8 encoding to ensure we're only outputting healthy, safe and effective HTML.
*/
function make_safe(string $content): string
{
return htmlentities($content, ENT_QUOTES, 'UTF-8');
}
/**
* Finalize admin page and output to browser.
*/
function display_admin($content, $title)
{
echo render('layouts/admin', [
'title' => $title,
'content' => $content,
]);
exit;
}
/**
* Determine what game skin to use. If a user is logged in then it uses their setting, otherwise defaults to 0 (retro).
*/
function game_skin(): int
{
return user() !== false ? user()->game_skin : 0;
}
/**
* Get a town's data by it's coordinates.
*/
function get_town_by_xy(int $x, int $y): array|false
{
$cache_tag = "town-$x-$y";
if (! isset($GLOBALS['cache'][$cache_tag])) {
$query = db()->query('SELECT * FROM towns WHERE longitude = ? AND latitude = ? LIMIT 1;', [$x, $y]);
if ($query === false) {
return false;
}
$GLOBALS['cache'][$cache_tag] = $query->fetchArray(SQLITE3_ASSOC);
}
return $GLOBALS['cache'][$cache_tag];
}
/**
* Get a town's data by it's ID.
*/
function get_town_by_id(int $id): array|false
{
$query = db()->query('SELECT * FROM towns WHERE id = ? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get a user's data by their ID, username or email.
*/
function get_user(int|string $id, string $data = '*'): array|false
{
$query = db()->query(
"SELECT $data FROM users WHERE id=? OR username=? COLLATE NOCASE OR email=? COLLATE NOCASE LIMIT 1;",
[$id, $id, $id]
);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get an item by it's ID.
*/
function get_item(int $id): array|false
{
$query = db()->query('SELECT * FROM items WHERE id=? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get a drop by it's ID.
*/
function get_drop(int $id): array|false
{
$query = db()->query('SELECT * FROM drops WHERE id=? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get a spell by it's ID.
*/
function get_spell(int $id): array|false
{
$query = db()->query('SELECT * FROM spells WHERE id=? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Get a monster by it's ID.
*/
function get_monster(int $id): array|false
{
$query = db()->query('SELECT * FROM monsters WHERE id=? LIMIT 1;', [$id]);
if ($query === false) {
return false;
}
return $query->fetchArray(SQLITE3_ASSOC);
}
/**
* Translate a Specials keyword to it's string.
*/
function special_to_string(string $special): string
{
return match ($special) {
'maxhp' => 'Max HP',
'maxmp' => 'Max MP',
'maxtp' => 'Max TP',
'goldbonus' => 'Gold Bonus (%)',
'expbonus' => 'Experience Bonus (%)',
'strength' => 'Strength',
'dexterity' => 'Dexterity',
'attackpower' => 'Attack Power',
'defensepower' => 'Defense Power',
default => $special
};
}
/**
* Generate a pretty dope token.
*/
function token($length = 32): string
{
return bin2hex(random_bytes($length));
}
/**
* Validate any given array of data against rules. Returns [valid, data, error]. Data contains the trimmed
* values from the input array. Note: all fields with rules are assumed to be required, unless the optional
* rule is used.
*
* Example: ['required', 'no-trim', 'length:5-20', 'alphanum-spaces']
*/
function validate(array $input_data, array $rules): array
{
$data = [];
$errors = [];
foreach ($rules as $field => $field_rules) {
$value = $input_data[$field] ?? null;
$field_name = ucfirst(str_replace('_', ' ', $field));
$is_required = true;
$default_value = null;
if (in_array('optional', $field_rules)) {
$is_required = false;
}
foreach ($field_rules as $rule) {
if (strpos($rule, 'default:') === 0) {
$default_value = substr($rule, 8);
break;
}
}
if (($value === null || $value === '') && $default_value !== null) {
$value = $default_value;
}
if (($value === null || $value === '') && ! $is_required) {
continue;
}
if ($is_required && ($value === null || $value === '')) {
$errors[$field][] = "{$field_name} is required.";
continue;
}
if (! in_array('no-trim', $field_rules)) {
$value = trim($value);
}
$data[$field] = $value;
foreach ($field_rules as $rule) {
// Parse rule and arguments
if (strpos($rule, ':') !== false) {
list($rule_name, $rule_args) = explode(':', $rule, 2);
} else {
$rule_name = $rule;
$rule_args = null;
}
if ($rule_name === 'optional') {
continue;
}
switch ($rule_name) {
case 'bool':
if (! isset($input_data[$field]) || empty($value)) {
$value = false;
} else {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($value === null) {
$errors[$field][] = "{$field_name} must be a valid boolean value.";
}
}
break;
case 'length':
list($min, $max) = explode('-', $rule_args);
$len = strlen((string) $value);
if ($len < $min || $len > $max) {
$errors[$field][] = "{$field_name} must be between {$min} and {$max} characters.";
}
break;
case 'alphanum':
if (! preg_match('/^[a-zA-Z0-9]+$/', $value)) {
$errors[$field][] = "{$field_name} must contain only letters and numbers.";
}
break;
case 'alpha':
if (! preg_match('/^[a-zA-Z]+$/', $value)) {
$errors[$field][] = "{$field_name} must contain only letters and numbers.";
}
break;
case 'alphanum-spaces':
if (! preg_match('/^[a-zA-Z0-9\s_]+$/', $value)) {
$errors[$field][] = "{$field_name} must contain only letters, numbers, spaces, and underscores.";
}
break;
case 'alpha-spaces':
if (! preg_match('/^[a-zA-Z\s_]+$/', $value)) {
$errors[$field][] = "{$field_name} must contain only letters, numbers, spaces, and underscores.";
}
break;
case 'email':
if (! filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$field][] = "{$field_name} must be a valid email address.";
}
break;
case 'int':
if (filter_var($value, FILTER_VALIDATE_INT) === false) {
$errors[$field][] = "{$field_name} must be an integer.";
}
break;
case 'min':
if ($value < $rule_args) {
$errors[$field][] = "{$field_name} must be at least {$rule_args}.";
}
break;
case 'max':
if ($value > $rule_args) {
$errors[$field][] = "{$field_name} must be no more than {$rule_args}.";
}
break;
case 'regex':
if (! preg_match($rule_args, $value)) {
$errors[$field][] = "{$field_name} does not match the required pattern.";
}
break;
case 'in':
$options = explode(',', $rule_args);
if (! in_array($value, $options)) {
$errors[$field][] = "{$field_name} must be one of: ".implode(', ', $options);
}
break;
case 'confirm':
$field_to_confirm = substr($field, 8);
$confirm_value = $data[$field_to_confirm] ?? '';
$confirm_field_name = ucfirst(str_replace('_', ' ', $field_to_confirm));
if ($value !== $confirm_value) {
$errors[$field][] = "{$field_name} must match {$confirm_field_name}.";
}
break;
case 'unique':
list($table, $column) = explode(',', $rule_args, 2);
if (db()->exists($table, $column, $value)) {
$errors[$field][] = "{$field_name} must be unique.";
}
break;
}
}
}
foreach ($input_data as $field => $value) {
if (! isset($data[$field])) {
$data[$field] = trim($value);
}
}
return [
'valid' => empty($errors),
'data' => $data,
'errors' => $errors,
];
}
/**
* Generates a ul list from `validate()`'s errors.
*/
function ul_from_validate_errors(array $errors): string
{
$string = '<ul>';
foreach ($errors as $field => $errors) {
$string .= '<li>';
foreach ($errors as $error) {
$string .= $error;
}
$string .= '</li>';
}
return $string.'</ul>';
}
/**
* Load the environment variables from the .env file.
*/
function env_load(string $filePath): void
{
if (! file_exists($filePath)) {
throw new \Exception('The .env file does not exist. (el)');
}
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
// Skip lines that are empty after trimming or are comments
if ($line === '' || str_starts_with($line, '#')) {
continue;
}
// Skip lines without an '=' character
if (strpos($line, '=') === false) {
continue;
}
[$name, $value] = explode('=', $line, 2);
$name = trim($name);
$value = trim($value, " \t\n\r\0\x0B\"'"); // Trim whitespace and quotes
if (! array_key_exists($name, $_SERVER) && ! array_key_exists($name, $_ENV)) {
putenv("$name=$value");
$_ENV[$name] = $value;
$_SERVER[$name] = $value;
}
}
}
/**
* Retrieve an environment variable.
*/
function env(string $key, mixed $default = null): mixed
{
$v = $_ENV[$key] ?? $_SERVER[$key] ?? (getenv($key) ?: $default);
return match (true) {
$v === 'true' => true,
$v === 'false' => false,
is_numeric($v) => (int) $v,
is_float($v) => (float) $v,
default => $v
};
}
/**
* Get the data on spells from a given list of IDs.
*/
function get_spells_from_list(array|string $spell_ids): array|false
{
if (is_string($spell_ids)) {
$spell_ids = explode(',', $spell_ids);
}
$placeholders = implode(',', array_fill(0, count($spell_ids), '?'));
$query = db()->query("SELECT id, name, type FROM spells WHERE id IN($placeholders)", $spell_ids);
if ($query === false) {
return false;
}
$rows = [];
while ($row = $query->fetchArray(SQLITE3_ASSOC)) {
$rows[] = $row;
}
return ! empty($rows) ? $rows : false;
}
function generate_stat_bar(int $current, int $max): string
{
$percent = $max > 0 ? round(max(0, $current) / $max * 100, 4) : 0;
if ($percent < 0) {
$percent = 0;
}
if ($percent > 100) {
$percent = 100;
}
$color = $percent >= 66 ? 'green' : ($percent >= 33 ? 'yellow' : 'red');
return <<<HTML
<div class="stat-bar" style="width: 15px; height: 100px; border: solid 1px black;">
<div style="height: $percent%; background-image: url(/img/bars_$color.gif);"></div>
</div>
HTML;
}
function create_stat_table(): string
{
$stat_table = '<div class="stat-table">'.
'<div class="stat-row">'.
'<div class="stat-col">'.generate_stat_bar((int) user()->currenthp, (int) user()->maxhp).'<div>HP</div></div>'.
'<div class="stat-col">'.generate_stat_bar((int) user()->currentmp, (int) user()->maxmp).'<div>MP</div></div>'.
'<div class="stat-col">'.generate_stat_bar((int) user()->currenttp, (int) user()->maxtp).'<div>TP</div></div>'.
'</div>'.
'</div>';
return $stat_table;
}
/**
* Returns the user in the GLOBALS state, if there is one. If not, populates it if there is a SESSION user_id.
*/
function user(): User|false
{
$GLOBALS['state']['user'] ??= (isset($_SESSION['user_id']) ? User::find($_SESSION['user_id']) : false);
return $GLOBALS['state']['user'];
}
/**
* Determine whether a request is from HTMX. If HTMX is trying to restore history, we will say no in order to render
* full pages.
*/
function is_htmx(): bool
{
if (isset($_SERVER['HTTP_HX_HISTORY_RESTORE_REQUEST']) && $_SERVER['HTTP_HX_HISTORY_RESTORE_REQUEST'] === 'true') {
return false;
}
return isset($_SERVER['HTTP_HX_REQUEST']) && $_SERVER['HTTP_HX_REQUEST'] === 'true';
}
/**
* Return whether the request is POST.
*/
function is_post(): bool
{
return $_SERVER['REQUEST_METHOD'] === 'POST';
}
/**
* Get the current page title per updates. Optionally set a new title.
*/
function page_title(string $new_title = ''): string
{
if ($new_title) {
return $GLOBALS['state']['new-page-title'] = $new_title;
}
return $GLOBALS['state']['new-page-title'] ?? env('game_name');
}
/**
* Render the response for the browser based on the request context. The main point is to seperate the handling
* of HTMX responses from normal responses.
*/
function render_response(array $uri, string $content): string
{
if ($uri[0] === 'babblebox') {
return $content;
}
if (is_htmx()) {
header('HX-Push-Url: '.$_SERVER['REQUEST_URI']);
$content .= '<title>'.page_title().'</title>';
$content .= Render::debug_db_info();
if (env('debug', false)) {
$content .= Render::debug_query_log();
}
if ($GLOBALS['state']['user-state-changed'] ?? false) {
$content .= Render::right_nav();
$content .= Render::left_nav();
}
}
return Render::content($content, page_layout());
}
/**
* Get/set page layout through GLOBALS state.
*/
function page_layout(string $layout = ''): string
{
if ($layout === '') {
return $GLOBALS['state']['page-layout'] ?? 'layouts/primary';
}
return $GLOBALS['state']['page-layout'] = $layout;
}

View File

@ -15,7 +15,7 @@ Experience values for each level should be the cumulative total amount of experi
<tr><td colspan="2"></td></tr> <tr><td colspan="2"></td></tr>
<?php foreach ([1, 2, 3] as $n): ?> <?php foreach ([1, 2, 3] as $n): ?>
<?php $class_name = DragonKnight\Lib::env("class_{$n}_name"); ?> <?php $class_name = DragonKnight\env("class_{$n}_name"); ?>
<tr><td><?php echo $class_name ?> EXP</td> <td><input type="number" name="<?php echo $n ?>_exp" value="<?php echo $level["{$n}_exp"] ?>"></td></tr> <tr><td><?php echo $class_name ?> EXP</td> <td><input type="number" name="<?php echo $n ?>_exp" value="<?php echo $level["{$n}_exp"] ?>"></td></tr>
<tr><td><?php echo $class_name ?> HP</td> <td><input type="number" name="<?php echo $n ?>_hp" value="<?php echo $level["{$n}_hp"] ?>"></td></tr> <tr><td><?php echo $class_name ?> HP</td> <td><input type="number" name="<?php echo $n ?>_hp" value="<?php echo $level["{$n}_hp"] ?>"></td></tr>
<tr><td><?php echo $class_name ?> MP</td> <td><input type="number" name="<?php echo $n ?>_mp" value="<?php echo $level["{$n}_mp"] ?>"></td></tr> <tr><td><?php echo $class_name ?> MP</td> <td><input type="number" name="<?php echo $n ?>_mp" value="<?php echo $level["{$n}_mp"] ?>"></td></tr>

View File

@ -23,9 +23,9 @@
<tr><td>Longitude</td><td><input type="number" name="longitude" value="<?php echo $user['longitude'] ?>" /></td></tr> <tr><td>Longitude</td><td><input type="number" name="longitude" value="<?php echo $user['longitude'] ?>" /></td></tr>
<tr><td>Character Class</td><td> <tr><td>Character Class</td><td>
<select name="charclass"> <select name="charclass">
<option value="1" <?php echo $user['charclass'] == 1 ? 'selected' : '' ?>><?php echo DragonKnight\Lib::env('class_1_name') ?></option> <option value="1" <?php echo $user['charclass'] == 1 ? 'selected' : '' ?>><?php echo DragonKnight\env('class_1_name') ?></option>
<option value="2" <?php echo $user['charclass'] == 2 ? 'selected' : '' ?>><?php echo DragonKnight\Lib::env('class_2_name') ?></option> <option value="2" <?php echo $user['charclass'] == 2 ? 'selected' : '' ?>><?php echo DragonKnight\env('class_2_name') ?></option>
<option value="3" <?php echo $user['charclass'] == 3 ? 'selected' : '' ?>><?php echo DragonKnight\Lib::env('class_3_name') ?></option> <option value="3" <?php echo $user['charclass'] == 3 ? 'selected' : '' ?>><?php echo DragonKnight\env('class_3_name') ?></option>
</select> </select>
</td></tr> </td></tr>

View File

@ -8,8 +8,8 @@
<td width="20%"><span class="highlight">Game Open:</span></td> <td width="20%"><span class="highlight">Game Open:</span></td>
<td> <td>
<select name="gameopen"> <select name="gameopen">
<option value="1" <?php echo DragonKnight\Lib::env('game_open') ? 'selected' : '' ?>>Open</option> <option value="1" <?php echo DragonKnight\env('game_open') ? 'selected' : '' ?>>Open</option>
<option value="0" <?php echo ! DragonKnight\Lib::env('game_open') ? 'selected' : '' ?>>Closed</option> <option value="0" <?php echo ! DragonKnight\env('game_open') ? 'selected' : '' ?>>Closed</option>
</select><br> </select><br>
<span class="small">Close the game if you are upgrading or working on settings and don't want to <span class="small">Close the game if you are upgrading or working on settings and don't want to
cause odd errors for end-users. Closing the game will completely halt all activity.</span> cause odd errors for end-users. Closing the game will completely halt all activity.</span>
@ -18,14 +18,14 @@
<tr> <tr>
<td width="20%">Game Name:</td> <td width="20%">Game Name:</td>
<td> <td>
<input type="text" name="gamename" value="<?php echo DragonKnight\Lib::env('game_name') ?>"><br> <input type="text" name="gamename" value="<?php echo DragonKnight\env('game_name') ?>"><br>
<span class="small">Change this if you want to change to call your game something different.</span> <span class="small">Change this if you want to change to call your game something different.</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="20%">Game URL:</td> <td width="20%">Game URL:</td>
<td> <td>
<input type="text" name="gameurl" value="<?php echo DragonKnight\Lib::env('game_url') ?>"><br> <input type="text" name="gameurl" value="<?php echo DragonKnight\env('game_url') ?>"><br>
<span class="small">Please specify the full URL to your game installation <span class="small">Please specify the full URL to your game installation
("https://www.dragonknight.com/"). This gets used in the registration email sent to users. If ("https://www.dragonknight.com/"). This gets used in the registration email sent to users. If
you leave this field blank or incorrect, users may not be able to register correctly.</span> you leave this field blank or incorrect, users may not be able to register correctly.</span>
@ -34,7 +34,7 @@
<tr> <tr>
<td width="20%">Admin Email:</td> <td width="20%">Admin Email:</td>
<td> <td>
<input type="text" name="adminemail" value="<?php echo DragonKnight\Lib::env('admin_email') ?>"><br> <input type="text" name="adminemail" value="<?php echo DragonKnight\env('admin_email') ?>"><br>
<span class="small">Please specify your email address. This gets used when the game has to send an <span class="small">Please specify your email address. This gets used when the game has to send an
email to users.</span> email to users.</span>
</td> </td>
@ -42,7 +42,7 @@
<tr> <tr>
<td width="20%">Map Size:</td> <td width="20%">Map Size:</td>
<td> <td>
<input type="number" name="gamesize" value="<?php echo DragonKnight\Lib::env('game_size') ?>"><br> <input type="number" name="gamesize" value="<?php echo DragonKnight\env('game_size') ?>"><br>
<span class="small"> <span class="small">
Default is 250. This is the size of each map quadrant. Note that monster Default is 250. This is the size of each map quadrant. Note that monster
levels increase every 5 spaces, so you should ensure that you have at least (map size / 5) levels increase every 5 spaces, so you should ensure that you have at least (map size / 5)
@ -55,8 +55,8 @@
<td width="20%">Email Verification:</td> <td width="20%">Email Verification:</td>
<td> <td>
<select name="verifyemail"> <select name="verifyemail">
<option value="0" <?php echo ! DragonKnight\Lib::env('verify_email') ? 'selected' : '' ?>>Disabled</option> <option value="0" <?php echo ! DragonKnight\env('verify_email') ? 'selected' : '' ?>>Disabled</option>
<option value="1" <?php echo DragonKnight\Lib::env('verify_email') ? 'selected' : '' ?>>Enabled</option> <option value="1" <?php echo DragonKnight\env('verify_email') ? 'selected' : '' ?>>Enabled</option>
</select><br> </select><br>
<span class="small">Make users verify their email address for added security.</span> <span class="small">Make users verify their email address for added security.</span>
</td> </td>
@ -65,8 +65,8 @@
<td width="20%">Show News:</td> <td width="20%">Show News:</td>
<td> <td>
<select name="shownews"> <select name="shownews">
<option value="0" <?php echo ! DragonKnight\Lib::env('show_news') ? 'selected' : '' ?>>No</option> <option value="0" <?php echo ! DragonKnight\env('show_news') ? 'selected' : '' ?>>No</option>
<option value="1" <?php echo DragonKnight\Lib::env('show_news') ? 'selected' : '' ?>>Yes</option> <option value="1" <?php echo DragonKnight\env('show_news') ? 'selected' : '' ?>>Yes</option>
</select><br> </select><br>
<span class="small">Toggle display of the Latest News box in towns. <span class="small">Toggle display of the Latest News box in towns.
</td> </td>
@ -75,8 +75,8 @@
<td width="20%">Show Who's Online:</td> <td width="20%">Show Who's Online:</td>
<td> <td>
<select name="showonline"> <select name="showonline">
<option value="0" <?php echo ! DragonKnight\Lib::env('show_online') ? 'selected' : '' ?>>No</option> <option value="0" <?php echo ! DragonKnight\env('show_online') ? 'selected' : '' ?>>No</option>
<option value="1" <?php echo DragonKnight\Lib::env('show_online') ? 'selected' : '' ?>>Yes</option> <option value="1" <?php echo DragonKnight\env('show_online') ? 'selected' : '' ?>>Yes</option>
</select><br> </select><br>
<span class="small">Toggle display of the Who's Online box in towns.</span> <span class="small">Toggle display of the Who's Online box in towns.</span>
</td> </td>
@ -85,23 +85,23 @@
<td width="20%">Show Babblebox:</td> <td width="20%">Show Babblebox:</td>
<td> <td>
<select name="showbabble"> <select name="showbabble">
<option value="0" <?php echo ! DragonKnight\Lib::env('show_babble') ? 'selected' : '' ?>>No</option> <option value="0" <?php echo ! DragonKnight\env('show_babble') ? 'selected' : '' ?>>No</option>
<option value="1" <?php echo DragonKnight\Lib::env('show_babble') ? 'selected' : '' ?>>Yes</option> <option value="1" <?php echo DragonKnight\env('show_babble') ? 'selected' : '' ?>>Yes</option>
</select><br> </select><br>
<span class="small">Toggle display of the Babble Box in towns.</span> <span class="small">Toggle display of the Babble Box in towns.</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="20%">Class 1 Name:</td> <td width="20%">Class 1 Name:</td>
<td><input type="text" name="class1name" value="<?php echo DragonKnight\Lib::env('class_1_name') ?>"><br></td> <td><input type="text" name="class1name" value="<?php echo DragonKnight\env('class_1_name') ?>"><br></td>
</tr> </tr>
<tr> <tr>
<td width="20%">Class 2 Name:</td> <td width="20%">Class 2 Name:</td>
<td><input type="text" name="class2name" value="<?php echo DragonKnight\Lib::env('class_2_name') ?>"><br></td> <td><input type="text" name="class2name" value="<?php echo DragonKnight\env('class_2_name') ?>"><br></td>
</tr> </tr>
<tr> <tr>
<td width="20%">Class 3 Name:</td> <td width="20%">Class 3 Name:</td>
<td><input type="text" name="class3name" value="<?php echo DragonKnight\Lib::env('class_3_name') ?>"><br></td> <td><input type="text" name="class3name" value="<?php echo DragonKnight\env('class_3_name') ?>"><br></td>
</tr> </tr>
</table> </table>

View File

@ -3,14 +3,14 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo DragonKnight\Lib::page_title() ?></title> <title><?php echo DragonKnight\page_title() ?></title>
<link rel="stylesheet" href="/css/admin.css"> <link rel="stylesheet" href="/css/admin.css">
<script src="/js/htmx.js"></script> <script src="/js/htmx.js"></script>
</head> </head>
<body> <body>
<div id="admin-container"> <div id="admin-container">
<header> <header>
<h1><?php echo DragonKnight\Lib::env('game_name') ?></h1> <h1><?php echo DragonKnight\env('game_name') ?></h1>
<h3>Admin</h3> <h3>Admin</h3>
</header> </header>
<main> <main>
@ -44,7 +44,7 @@
<div>Version <?php echo VERSION ?> <?php echo BUILD ?></div> <div>Version <?php echo VERSION ?> <?php echo BUILD ?></div>
</footer> </footer>
<?php if (DragonKnight\Lib::env('debug', false)) { <?php if (DragonKnight\env('debug', false)) {
echo DragonKnight\Render::debug_query_log(); echo DragonKnight\Render::debug_query_log();
} ?> } ?>
</div> </div>

View File

@ -3,12 +3,12 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo DragonKnight\Lib::env('game_name', 'Dragon Knight') ?> Help</title> <title><?php echo DragonKnight\env('game_name', 'Dragon Knight') ?> Help</title>
<link rel="stylesheet" href="/css/help.css"> <link rel="stylesheet" href="/css/help.css">
</head> </head>
<body> <body>
<a name="top"></a> <a name="top"></a>
<h1><?php echo DragonKnight\Lib::env('game_name', 'Dragon Knight') ?> Help</h1> <h1><?php echo DragonKnight\env('game_name', 'Dragon Knight') ?> Help</h1>
[ <a href="/help">Back to Help</a> ]<br> [ <a href="/help">Back to Help</a> ]<br>
[ <a href="/">Return to Game</a> ] [ <a href="/">Return to Game</a> ]

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo DragonKnight\Lib::page_title() ?></title> <title><?php echo DragonKnight\page_title() ?></title>
<link rel="stylesheet" href="/css/dk.css"> <link rel="stylesheet" href="/css/dk.css">
<script src="/js/htmx.js"></script> <script src="/js/htmx.js"></script>
@ -20,12 +20,12 @@
} }
</script> </script>
</head> </head>
<body class="skin-<?php echo DragonKnight\Lib::game_skin() ?>"> <body class="skin-<?php echo DragonKnight\game_skin() ?>">
<div id="game-container"> <div id="game-container">
<header> <header>
<a href="/"><img id="logo" src="/img/logo.gif" alt="<?php echo DragonKnight\Lib::env('game_name', 'Dragon Knight') ?>" title="<?php echo DragonKnight\Lib::env('game_name', 'Dragon Knight') ?>"></a> <a href="/"><img id="logo" src="/img/logo.gif" alt="<?php echo DragonKnight\env('game_name', 'Dragon Knight') ?>" title="<?php echo DragonKnight\env('game_name', 'Dragon Knight') ?>"></a>
<nav> <nav>
<?php if (DragonKnight\Lib::user() !== false): ?> <?php if (DragonKnight\user() !== false): ?>
<a href='/logout'><img src='/img/button_logout.gif' alt='Log Out' title='Log Out'></a> <a href='/logout'><img src='/img/button_logout.gif' alt='Log Out' title='Log Out'></a>
<?php else: ?> <?php else: ?>
<a href='/login'><img src='/img/button_login.gif' alt='Log In' title='Log In'></a> <a href='/login'><img src='/img/button_login.gif' alt='Log In' title='Log In'></a>
@ -48,7 +48,7 @@
<div>Version <?php echo VERSION ?> <?php echo BUILD ?></div> <div>Version <?php echo VERSION ?> <?php echo BUILD ?></div>
</footer> </footer>
<?php if (DragonKnight\Lib::env('debug', false)) { <?php if (DragonKnight\env('debug', false)) {
echo DragonKnight\Render::debug_query_log(); echo DragonKnight\Render::debug_query_log();
} ?> } ?>
</div> </div>

View File

@ -1,9 +1,9 @@
<section> <section>
<div class="title"><img src="/img/button_location.gif" alt="Location" title="Location"></div> <div class="title"><img src="/img/button_location.gif" alt="Location" title="Location"></div>
Currently: <?php echo DragonKnight\Lib::user()->currentaction ?><br> Currently: <?php echo DragonKnight\user()->currentaction ?><br>
<?php <?php
$lat = DragonKnight\Lib::user()->latitude; $lat = DragonKnight\user()->latitude;
$lon = DragonKnight\Lib::user()->longitude; $lon = DragonKnight\user()->longitude;
if ($lat < 0) { if ($lat < 0) {
$lat = ($lat * -1).'S'; $lat = ($lat * -1).'S';
} else { } else {
@ -31,15 +31,15 @@
<section> <section>
<div class="title"><img src="/img/button_towns.gif" alt="Towns" title="Towns"></div> <div class="title"><img src="/img/button_towns.gif" alt="Towns" title="Towns"></div>
<?php <?php
if (DragonKnight\Lib::user()->currentaction == 'In Town') { if (DragonKnight\user()->currentaction == 'In Town') {
$town = DragonKnight\Lib::get_town_by_xy((int) DragonKnight\Lib::user()->latitude, (int) DragonKnight\Lib::user()->longitude); $town = DragonKnight\get_town_by_xy((int) DragonKnight\user()->latitude, (int) DragonKnight\user()->longitude);
echo "Welcome to <b>{$town['name']}</b>.<br><br>"; echo "Welcome to <b>{$town['name']}</b>.<br><br>";
} }
?> ?>
Travel To:<br> Travel To:<br>
<?php <?php
$town_list = explode(',', DragonKnight\Lib::user()->towns); $town_list = explode(',', DragonKnight\user()->towns);
$towns = DragonKnight\Lib::db()->query('SELECT * FROM towns ORDER BY id;'); $towns = DragonKnight\db()->query('SELECT * FROM towns ORDER BY id;');
$mapped = false; $mapped = false;
while ($row = $towns->fetchArray(SQLITE3_ASSOC)) { while ($row = $towns->fetchArray(SQLITE3_ASSOC)) {
$mapped = true; $mapped = true;
@ -60,7 +60,7 @@
<a href="/" hx-get="/" hx-target="#middle">Home</a><br> <a href="/" hx-get="/" hx-target="#middle">Home</a><br>
<a href="/forum" hx-get="/forum" hx-target="#middle">Forum</a><br> <a href="/forum" hx-get="/forum" hx-target="#middle">Forum</a><br>
<a href="/settings">Settings</a><br> <a href="/settings">Settings</a><br>
<?php if (DragonKnight\Lib::user()->authlevel === 1): ?> <?php if (DragonKnight\user()->authlevel === 1): ?>
<a href="/admin">Admin</a><br> <a href="/admin">Admin</a><br>
<?php endif; ?> <?php endif; ?>
<a href="/help">Help</a><br> <a href="/help">Help</a><br>

View File

@ -9,9 +9,9 @@
<td>Character Class:</td> <td>Character Class:</td>
<td> <td>
<select name="charclass"> <select name="charclass">
<option value="1"><?php echo DragonKnight\Lib::env('class_1_name') ?></option> <option value="1"><?php echo DragonKnight\env('class_1_name') ?></option>
<option value="2"><?php echo DragonKnight\Lib::env('class_2_name') ?></option> <option value="2"><?php echo DragonKnight\env('class_2_name') ?></option>
<option value="3"><?php echo DragonKnight\Lib::env('class_3_name') ?></option> <option value="3"><?php echo DragonKnight\env('class_3_name') ?></option>
</select> </select>
</td> </td>
</tr> </tr>

View File

@ -1,30 +1,30 @@
<section> <section>
<div class="title"><img src="/img/button_character.gif" alt="Character" title="Character"></div> <div class="title"><img src="/img/button_character.gif" alt="Character" title="Character"></div>
<b><?php echo DragonKnight\Lib::user()->username ?></b><br> <b><?php echo DragonKnight\user()->username ?></b><br>
Level: <?php echo DragonKnight\Lib::user()->level ?><br> Level: <?php echo DragonKnight\user()->level ?><br>
Exp: <?php echo number_format(DragonKnight\Lib::user()->experience) ?><br> Exp: <?php echo number_format(DragonKnight\user()->experience) ?><br>
Gold: <?php echo number_format(DragonKnight\Lib::user()->gold) ?><br> Gold: <?php echo number_format(DragonKnight\user()->gold) ?><br>
HP: <?php echo $hp ?><br> HP: <?php echo $hp ?><br>
MP: <?php echo $mp ?><br> MP: <?php echo $mp ?><br>
TP: <?php echo DragonKnight\Lib::user()->currenttp ?><br><br> TP: <?php echo DragonKnight\user()->currenttp ?><br><br>
<?php echo DragonKnight\Lib::create_stat_table() ?><br> <?php echo DragonKnight\create_stat_table() ?><br>
<a href="javascript:opencharpopup()">Extended Stats</a> <a href="javascript:opencharpopup()">Extended Stats</a>
</section> </section>
<section> <section>
<div class="title"><img src="/img/button_inventory.gif" alt="Inventory" title="Inventory"></div> <div class="title"><img src="/img/button_inventory.gif" alt="Inventory" title="Inventory"></div>
<img src="/img/icon_weapon.gif" alt="Weapon" title="Weapon"> <?php echo DragonKnight\Lib::user()->weaponname ?><br> <img src="/img/icon_weapon.gif" alt="Weapon" title="Weapon"> <?php echo DragonKnight\user()->weaponname ?><br>
<img src="/img/icon_armor.gif" alt="Armor" title="Armor"> <?php echo DragonKnight\Lib::user()->armorname ?><br> <img src="/img/icon_armor.gif" alt="Armor" title="Armor"> <?php echo DragonKnight\user()->armorname ?><br>
<img src="/img/icon_shield.gif" alt="Shield" title="Shield"> <?php echo DragonKnight\Lib::user()->shieldname ?><br> <img src="/img/icon_shield.gif" alt="Shield" title="Shield"> <?php echo DragonKnight\user()->shieldname ?><br>
Slot 1: <?php echo DragonKnight\Lib::user()->slot1name ?><br> Slot 1: <?php echo DragonKnight\user()->slot1name ?><br>
Slot 2: <?php echo DragonKnight\Lib::user()->slot2name ?><br> Slot 2: <?php echo DragonKnight\user()->slot2name ?><br>
Slot 3: <?php echo DragonKnight\Lib::user()->slot3name ?> Slot 3: <?php echo DragonKnight\user()->slot3name ?>
</section> </section>
<section> <section>
<div class="title"><img src="/img/button_fastspells.gif" alt="Fast Spells" title="Fast Spells"></div> <div class="title"><img src="/img/button_fastspells.gif" alt="Fast Spells" title="Fast Spells"></div>
<?php <?php
$user_spells = DragonKnight\Lib::user()->spells(); $user_spells = DragonKnight\user()->spells();
if ($user_spells !== false) { if ($user_spells !== false) {
foreach ($user_spells as $spell) { foreach ($user_spells as $spell) {
// list only healing spells for now // list only healing spells for now

View File

@ -2,9 +2,9 @@
<b><?php echo $char->username ?></b><br><br> <b><?php echo $char->username ?></b><br><br>
Class: <?php echo match ($char->charclass) { Class: <?php echo match ($char->charclass) {
1 => DragonKnight\Lib::env('class_1_name'), 1 => DragonKnight\env('class_1_name'),
2 => DragonKnight\Lib::env('class_2_name'), 2 => DragonKnight\env('class_2_name'),
3 => DragonKnight\Lib::env('class_3_name') 3 => DragonKnight\env('class_3_name')
}; ?><br><br> }; ?><br><br>
Level: <?php echo $char->level ?><br> Level: <?php echo $char->level ?><br>