So many changes... sheesh. Adds HTMX, reworks rendering, etc

This commit is contained in:
Sky Johnson 2024-12-18 10:00:41 -06:00
parent 169e617989
commit 1b9106fadb
18 changed files with 316 additions and 210 deletions

View File

@ -96,6 +96,7 @@ a {
color: #663300; color: #663300;
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
cursor: pointer;
} }
a:hover { a:hover {

View File

@ -8,7 +8,7 @@ html {
} }
body { body {
background-image: url('/img/background.jpg'); background-image: url('/img/backgrounds/background.jpg');
padding: 2rem; padding: 2rem;
} }
table { table {

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -8,7 +8,7 @@ $r = new Router;
$r->get('/', function() { $r->get('/', function() {
if (user()->currentaction === "In Town") { if (user()->currentaction === "In Town") {
$page = dotown(); $page = Towns\town();
$title = "In Town"; $title = "In Town";
} elseif (user()->currentaction === "Exploring") { } elseif (user()->currentaction === "Exploring") {
$page = doexplore(); $page = doexplore();
@ -17,7 +17,7 @@ $r->get('/', function() {
redirect('/fight'); redirect('/fight');
} }
display($page, $title); return is_htmx() ? $page : display($page, $title);
}); });
$r->get('/ninja', function() { $r->get('/ninja', function() {
@ -45,83 +45,15 @@ $l = $r->lookup($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
if (is_int($l)) exit("Error: $l"); if (is_int($l)) exit("Error: $l");
if (!empty($l['middleware'])) foreach ($l['middleware'] as $middleware) $middleware(); if (!empty($l['middleware'])) foreach ($l['middleware'] as $middleware) $middleware();
$l['handler'](...$l['params'] ?? []); $content = $l['handler'](...$l['params'] ?? []);
if (is_htmx()) {
function donothing() $content .= Render\debug_db_info();
{ if ($GLOBALS['state']['user-state-changed'] ?? false) {
if (user()->currentaction == "In Town") { $content .= Render\right_nav();
$page = dotown();
$title = "In Town";
} elseif (user()->currentaction == "Exploring") {
$page = doexplore();
$title = "Exploring";
} elseif (user()->currentaction == "Fighting") {
redirect('/fight');
} }
display($page, $title);
}
/**
* Spit out the main town page.
*/
function dotown()
{
global $controlrow;
$townrow = get_town_by_xy(user()->longitude, user()->latitude);
if ($townrow === false) display("There is an error with your user account, or with the town data. Please try again.","Error");
$townrow["news"] = "";
$townrow["whosonline"] = "";
$townrow["babblebox"] = "";
// News box. Grab latest news entry and display it. Something a little more graceful coming soon maybe.
if ($controlrow["shownews"] == 1) {
$newsrow = db()->query('SELECT * FROM news ORDER BY id DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC);
$townrow["news"] = '<div class="title">Latest News</div>';
$townrow["news"] .= "<span class=\"light\">[".pretty_date($newsrow["postdate"])."]</span><br>".nl2br($newsrow["content"]);
}
// Who's Online. Currently just members. Guests maybe later.
if ($controlrow["showonline"] == 1) {
$onlinequery = db()->query(<<<SQL
SELECT id, username
FROM users
WHERE onlinetime >= datetime('now', '-600 seconds')
ORDER BY username;
SQL);
$online_count = 0;
$online_rows = [];
while ($onlinerow = $onlinequery->fetchArray(SQLITE3_ASSOC)) {
$online_count++;
$online_rows[] = "<a href=\"javascript:opencharpopup({$onlinerow['id']})\">".$onlinerow["username"]."</a>";
}
$townrow["whosonline"] = '<div class="title">Who\'s Online</div>';
$townrow["whosonline"] .= "There are <b>$online_count</b> user(s) online within the last 10 minutes: ";
$townrow["whosonline"] .= rtrim(implode(', ', $online_rows), ', ');
}
if ($controlrow["showbabble"] == 1) {
$townrow["babblebox"] = <<<HTML
<div class="title">Babble Box</div>
<iframe src="/babblebox" name="sbox" width="100%" height="250" frameborder="0" id="bbox">
Your browser does not support inline frames! The Babble Box will not be available until you upgrade to
a newer <a href="http://www.mozilla.org" target="_new">browser</a>.
</iframe>
HTML;
}
$u = User::find(1);
$u->gold += 100;
$u->save();
var_dump($u->gold);
return render('towns', ['town' => $townrow]);
} }
echo $content;
exit;
/** /**
* Just spit out a blank exploring page. Exploring without a GET string is normally when they first log in, or when * Just spit out a blank exploring page. Exploring without a GET string is normally when they first log in, or when
@ -130,12 +62,8 @@ function dotown()
function doexplore() function doexplore()
{ {
return <<<HTML return <<<HTML
<table width="100%"> <div class="title"><img src="/img/title_exploring.gif" alt="Exploring"></div>
<tr><td class="title"><img src="/img/title_exploring.gif" alt="Exploring" /></td></tr>
<tr><td>
You are exploring the map, and nothing has happened. Continue exploring using the direction buttons or the Travel To menus. You are exploring the map, and nothing has happened. Continue exploring using the direction buttons or the Travel To menus.
</td></tr>
</table>
HTML; HTML;
} }
@ -167,7 +95,7 @@ function show_character_info(int $id = 0): void
'magic_list' => $magic_list, 'magic_list' => $magic_list,
'controlrow' => $controlrow 'controlrow' => $controlrow
]); ]);
echo render('minimal', ['content' => $showchar, 'title' => $userrow['username'].' Information']); echo render('layouts/minimal', ['content' => $showchar, 'title' => $userrow['username'].' Information']);
} }
function showmap() function showmap()
@ -178,7 +106,7 @@ function showmap()
round(258 - user()->latitude * (500 / 500) - 3) round(258 - user()->latitude * (500 / 500) - 3)
); );
echo render('minimal', [ echo render('layouts/minimal', [
'content' => '<img src="/img/map.gif" alt="Map">'.$pos, 'content' => '<img src="/img/map.gif" alt="Map">'.$pos,
'title' => 'Map' 'title' => 'Map'
]); ]);

1
public/js/htmx.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -495,7 +495,7 @@ function levels()
function display_help(string $content) function display_help(string $content)
{ {
global $controlrow; global $controlrow;
echo render('help', [ echo render('layouts/help', [
'control' => $controlrow, 'control' => $controlrow,
'content' => $content, 'content' => $content,
'version' => VERSION, 'version' => VERSION,

View File

@ -20,40 +20,99 @@ function register_routes(Router $r): Router
return $r; return $r;
} }
/**
* Spit out the main town page.
*/
function town()
{
global $controlrow;
$townrow = get_town_by_xy(user()->longitude, user()->latitude);
if ($townrow === false) display("There is an error with your user account, or with the town data. Please try again.","Error");
$townrow["news"] = "";
$townrow["whosonline"] = "";
$townrow["babblebox"] = "";
// News box. Grab latest news entry and display it. Something a little more graceful coming soon maybe.
if ($controlrow["shownews"] == 1) {
$newsrow = db()->query('SELECT * FROM news ORDER BY id DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC);
$townrow["news"] = '<div class="title">Latest News</div>';
$townrow["news"] .= "<span class=\"light\">[".pretty_date($newsrow["postdate"])."]</span><br>".nl2br($newsrow["content"]);
}
// Who's Online. Currently just members. Guests maybe later.
if ($controlrow["showonline"] == 1) {
$onlinequery = db()->query(<<<SQL
SELECT id, username
FROM users
WHERE onlinetime >= datetime('now', '-600 seconds')
ORDER BY username;
SQL);
$online_count = 0;
$online_rows = [];
while ($onlinerow = $onlinequery->fetchArray(SQLITE3_ASSOC)) {
$online_count++;
$online_rows[] = "<a href=\"javascript:opencharpopup({$onlinerow['id']})\">".$onlinerow["username"]."</a>";
}
$townrow["whosonline"] = '<div class="title">Who\'s Online</div>';
$townrow["whosonline"] .= "There are <b>$online_count</b> user(s) online within the last 10 minutes: ";
$townrow["whosonline"] .= rtrim(implode(', ', $online_rows), ', ');
}
if ($controlrow["showbabble"] == 1) {
$townrow["babblebox"] = <<<HTML
<div class="title">Babble Box</div>
<iframe src="/babblebox" name="sbox" width="100%" height="250" frameborder="0" id="bbox">
Your browser does not support inline frames! The Babble Box will not be available until you upgrade to
a newer <a href="http://www.mozilla.org" target="_new">browser</a>.
</iframe>
HTML;
}
if (is_htmx()) htmx_update_page_title($townrow['name']);
return render('towns', ['town' => $townrow]);
}
/** /**
* Staying at the inn resets all expendable stats to their max values. * Staying at the inn resets all expendable stats to their max values.
*/ */
function inn() function inn()
{ {
$townrow = get_town_by_xy(user()->longitude, user()->latitude); $town = get_town_by_xy(user()->longitude, user()->latitude);
if ($townrow === false) { display("Cheat attempt detected.<br><br>Get a life, loser.", "Error"); } if ($town === false) { exit('Cheat attempt detected.<br><br>Get a life, loser.'); }
if (user()->gold < $townrow["innprice"]) { $htmx = is_htmx();
display("You do not have enough gold to stay at this Inn tonight.<br><br>You may return to <a href=\"/\">town</a>, or use the direction buttons on the left to start exploring.", "Inn"); if ($htmx) htmx_update_page_title($town['name'] . ' Inn');
}
if (isset($_POST["submit"])) { if (user()->gold < $town['innprice']) {
$newgold = user()->gold - $townrow["innprice"]; $page = <<<HTML
db()->query( You do not have enough gold to stay at this Inn tonight. <br><br>
'UPDATE users SET gold=?, currenthp=?, currentmp=?, currenttp=? WHERE id=?', You may return to <a hx-get="/" hx-target="#middle">town</a>, or use the direction buttons on the left to start exploring.
[$newgold, user()->maxhp, user()->maxmp, user()->maxtp, user()->id HTML;
]); } elseif (isset($_POST['submit'])) {
$title = "Inn"; user()->gold -= $town['innprice'];
$page = "You wake up feeling refreshed and ready for action.<br><br>You may return to <a href=\"/\">town</a>, or use the direction buttons on the left to start exploring."; user()->restore_points()->save();
} elseif (isset($_POST["cancel"])) { $page = <<<HTML
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.
HTML;
} elseif (isset($_POST['cancel'])) {
redirect('/'); redirect('/');
} else { } else {
$title = "Inn";
$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>
A night's sleep at this Inn will cost you <b>{$townrow["innprice"]} gold</b>. Is that ok?<br><br> A night's sleep at this Inn will cost you <b>{$town['innprice']} gold</b>. Is that ok?<br><br>
<form action="/inn" method="post"> <form hx-post="/inn" hx-target="#middle">
<input type="submit" name="submit" value="Yes"> <input type="submit" name="cancel" value="No"> <input type="submit" name="submit" value="Yes"> <input type="submit" name="cancel" value="No">
</form> </form>
HTML; HTML;
} }
display($page, $title); return $htmx ? $page : display($page, $town['name'] . ' Inn');
} }
/** /**
@ -61,32 +120,51 @@ function inn()
*/ */
function buy() function buy()
{ {
$townrow = get_town_by_xy(user()->longitude, user()->latitude); $town = get_town_by_xy(user()->longitude, user()->latitude);
if ($townrow === false) display("Cheat attempt detected.<br><br>Get a life, loser.", "Error"); if ($town === false) { exit('Cheat attempt detected.<br><br>Get a life, loser.'); }
$items = db()->query("SELECT * FROM items WHERE id IN ({$townrow["itemslist"]});"); $htmx = is_htmx();
$page = "Buying weapons will increase your Attack Power. Buying armor and shields will increase your Defense Power.<br><br>Click an item name to purchase it.<br><br>The following items are available at this town:<br><br>\n"; if ($htmx) htmx_update_page_title($town['name'] . ' Shop');
$page .= "<table width=\"80%\">\n";
while ($itemsrow = $items->fetchArray(SQLITE3_ASSOC)) { $page = <<<HTML
$attrib = ($itemsrow["type"] == 1) ? "Attack Power:" : "Defense Power:"; Buying weapons will increase your Attack Power. Buying armor and shields will increase your Defense Power.<br><br>
$page .= "<tr><td width=\"4%\">"; Click an item name to purchase it.<br><br>
$page .= match ($itemsrow["type"]) { The following items are available at this town:<br><br>
1 => '<img src="/img/icon_weapon.gif" alt="weapon" /></td>', <table>
2 => '<img src="/img/icon_armor.gif" alt="armor" /></td>', HTML;
3 => '<img src="/img/icon_shield.gif" alt="shield" /></td>'
$items = db()->query('SELECT * FROM items WHERE id IN (' . $town["itemslist"] . ');');
while ($item = $items->fetchArray(SQLITE3_ASSOC)) {
$attrib = ($item["type"] == 1) ? "Attack Power:" : "Defense Power:";
$page .= '<tr><td width="4%">';
$page .= match ($item["type"]) {
1 => '<img src="/img/icon_weapon.gif" alt="weapon">',
2 => '<img src="/img/icon_armor.gif" alt="armor">',
3 => '<img src="/img/icon_shield.gif" alt="shield">'
}; };
if (user()->weaponid == $itemsrow["id"] || user()->armorid == $itemsrow["id"] || user()->shieldid == $itemsrow["id"]) { $page .= '</td>';
$page .= "<td width=\"32%\"><span class=\"light\">".$itemsrow["name"]."</span></td><td width=\"32%\"><span class=\"light\">$attrib ".$itemsrow["attribute"]."</span></td><td width=\"32%\"><span class=\"light\">Already purchased</span></td></tr>\n"; if (user()->weaponid === $item["id"] || user()->armorid === $item["id"] || user()->shieldid === $item["id"]) {
$page .= <<<HTML
<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">Already purchased</span></td>
HTML;
} else { } else {
if ($itemsrow["special"] != "X") { $specialdot = "<span class=\"highlight\">&#42;</span>"; } else { $specialdot = ""; } $specialdot = $item['special'] !== 'X' ? '<span class="highlight">&#42;</span>' : '';
$page .= "<td width=\"32%\"><b><a href=\"/buy2/{$itemsrow["id"]}\">".$itemsrow["name"]."</a>$specialdot</b></td><td width=\"32%\">$attrib <b>".$itemsrow["attribute"]."</b></td><td width=\"32%\">Price: <b>".$itemsrow["buycost"]." gold</b></td></tr>\n"; $page .= <<<HTML
<td width="32%"><b><a href="/buy2/{$item['id']}">{$item['name']}</a>$specialdot</b></td>
<td width="32%">$attrib <b>{$item['attribute']}</b></td>
<td width="32%">Price: <b>{$item['buycost']} gold</b></td>
HTML;
} }
$page .= '</tr>';
} }
$page .= "</table><br>\n"; $page .= <<<HTML
$page .= "If you've changed your mind, you may also return back to <a href=\"/\">town</a>.\n"; </table><br>
$title = "Buy Items"; If you've changed your mind, you may also return back to <a hx-get="/" hx-target="#middle">town</a>.
HTML;
display($page, $title); return $htmx ? $page : display($page, $town['name'] . ' Shop');
} }
/** /**

View File

@ -4,6 +4,7 @@ require_once 'lib.php';
require_once 'router.php'; require_once 'router.php';
require_once 'auth.php'; require_once 'auth.php';
require_once 'mail.php'; require_once 'mail.php';
require_once 'render.php';
require_once 'actions/explore.php'; require_once 'actions/explore.php';
require_once 'actions/heal.php'; require_once 'actions/heal.php';
require_once 'actions/users.php'; require_once 'actions/users.php';

View File

@ -19,7 +19,13 @@ function db(): Database
*/ */
function redirect(string $location): void function redirect(string $location): void
{ {
if (is_htmx()) {
header("HX-Redirect: $location");
header("HX-Replace-Url: $location");
} else {
header("Location: $location"); header("Location: $location");
}
exit; exit;
} }
@ -82,7 +88,7 @@ function make_safe(string $content): string
*/ */
function display_admin($content, $title) function display_admin($content, $title)
{ {
echo render('admin', [ echo render('layouts/admin', [
"title" => $title, "title" => $title,
"content" => $content, "content" => $content,
"totaltime" => round(microtime(true) - START, 4), "totaltime" => round(microtime(true) - START, 4),
@ -97,28 +103,17 @@ function display_admin($content, $title)
/** /**
* Finalize page and output to browser. * Finalize page and output to browser.
*/ */
function display($content, $title, bool $topnav = true, bool $leftnav = true, bool $rightnav = true): void function display($content, $title, bool $topnav = true, bool $leftnav = true, bool $rightnav = true): string
{ {
global $controlrow; global $controlrow;
if ($topnav == true) { $game_skin = 0;
if (user() !== false) { // user should be logged in
$topnav = <<<HTML $topnav = $topnav ? Render\header_links() : '';
<a href='/logout'><img src='/img/button_logout.gif' alt='Log Out' title='Log Out'></a>
<a href='/help'><img src='/img/button_help.gif' alt='Help' title='Help'></a>
HTML;
} else {
$topnav = <<<HTML
<a href='/login'><img src='/img/button_login.gif' alt='Log In' title='Log In'></a>
<a href='/register'><img src='/img/button_register.gif' alt='Register' title='Register'></a>
<a href='/help'><img src='/img/button_help.gif' alt='Help' title='Help'></a>
HTML;
}
} else {
$topnav = '';
}
if (user() !== false) { if (user() !== false) {
$game_skin = user()->game_skin;
if (user()->currentaction == 'In Town') { if (user()->currentaction == 'In Town') {
$town = get_town_by_xy(user()->latitude, user()->longitude); $town = get_town_by_xy(user()->latitude, user()->longitude);
$current_town = "Welcome to <b>{$town['name']}</b>.<br><br>"; $current_town = "Welcome to <b>{$town['name']}</b>.<br><br>";
@ -129,26 +124,6 @@ function display($content, $title, bool $topnav = true, bool $leftnav = true, bo
// Format various userrow stuffs... // Format various userrow stuffs...
if (user()->latitude < 0) { user()->latitude = user()->latitude * -1 . "S"; } else { user()->latitude .= "N"; } if (user()->latitude < 0) { user()->latitude = user()->latitude * -1 . "S"; } else { user()->latitude .= "N"; }
if (user()->longitude < 0) { user()->longitude = user()->longitude * -1 . "W"; } else { user()->longitude .= "E"; } if (user()->longitude < 0) { user()->longitude = user()->longitude * -1 . "W"; } else { user()->longitude .= "E"; }
user()->experience = number_format(user()->experience);
user()->gold = number_format(user()->gold);
// Now make numbers stand out if they're low.
if (user()->currenthp <= (user()->maxhp/5)) { user()->currenthp = "<blink><span class=\"highlight\"><b>*".user()->currenthp."*</b></span></blink>"; }
if (user()->currentmp <= (user()->maxmp/5)) { user()->currentmp = "<blink><span class=\"highlight\"><b>*".user()->currentmp."*</b></span></blink>"; }
$user_spells = explode(',', user()->spells);
$spellquery = get_spells_from_list($user_spells);
user()->magiclist = '';
while ($spell = $spellquery->fetchArray(SQLITE3_ASSOC)) {
$spell = false;
foreach($user_spells as $id) {
if ($id === $spell['id'] && $spell['type'] == 1) $spell = true;
}
if ($spell == true) {
user()->magiclist .= "<a href=\"/spell/{$spell['id']}\">".$spell['name']."</a><br>";
}
}
if (user()->magiclist == "") { user()->magiclist = "None"; }
// Travel To list. // Travel To list.
$townslist = explode(",",user()->towns); $townslist = explode(",",user()->towns);
@ -165,22 +140,14 @@ function display($content, $title, bool $topnav = true, bool $leftnav = true, bo
} }
} }
echo render('primary', [ return render('layouts/primary', [
"dkgamename" => $controlrow["gamename"], "dkgamename" => $controlrow["gamename"],
"title" => $title, "title" => $title,
"content" => $content, "content" => $content,
"game_skin" => user()->game_skin ??= '0', "game_skin" => $game_skin,
'rightnav' => $rightnav ? render('rightnav', ['statbars' => create_stat_table(user())]) : '',
"leftnav" => $leftnav ? render('leftnav', ['town_list' => $town_list_html, 'current_town' => $current_town]) : '', "leftnav" => $leftnav ? render('leftnav', ['town_list' => $town_list_html, 'current_town' => $current_town]) : '',
"topnav" => $topnav, "topnav" => $topnav,
"totaltime" => round(microtime(true) - START, 4),
"numqueries" => db()->count,
"version" => VERSION,
"build" => BUILD,
"querylog" => env('debug', false) ? db()->log : []
]); ]);
exit;
} }
function checkcookies() function checkcookies()
@ -575,32 +542,36 @@ function env(string $key, mixed $default = null): mixed
/** /**
* Get the data on spells from a given list of IDs. * Get the data on spells from a given list of IDs.
*/ */
function get_spells_from_list(array|string $spell_ids): SQLite3Result|false function get_spells_from_list(array|string $spell_ids): array|false
{ {
if (is_string($spell_ids)) $spell_ids = explode(',', $spell_ids); if (is_string($spell_ids)) $spell_ids = explode(',', $spell_ids);
$placeholders = implode(',', array_fill(0, count($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); $query = db()->query("SELECT id, name, type FROM spells WHERE id IN($placeholders)", $spell_ids);
if ($query === false) return false; if ($query === false) return false;
return $query; $rows = [];
while ($row = $query->fetchArray(SQLITE3_ASSOC)) $rows[] = $row;
return !empty($rows) ? $rows : false;
} }
function generate_stat_bar($current, $max) function generate_stat_bar(int $current, int $max): string
{ {
$percent = ($max === 0) ? 0 : ceil($current / $max * 100); $percent = $max > 0 ? round(max(0, $current) / $max * 100, 4) : 0;
$color = $percent >= 66 ? 'green' : ($percent >= 33 ? 'yellow' : 'red'); $color = $percent >= 66 ? 'green' : ($percent >= 33 ? 'yellow' : 'red');
return '<div class="stat-bar" style="width: 15px; height: 100px; border: solid 1px black;">' . return <<<HTML
'<div style="height: ' . $percent . 'px; background-image: url(/img/bars_' . $color . '.gif);"></div>' . <div class="stat-bar" style="width: 15px; height: 100px; border: solid 1px black;">
'</div>'; <div style="height: $percent%; background-image: url(/img/bars_$color.gif);"></div>
</div>
HTML;
} }
function create_stat_table($userrow) function create_stat_table(): string
{ {
$stat_table = '<div class="stat-table">' . $stat_table = '<div class="stat-table">' .
'<div class="stat-row">' . '<div class="stat-row">' .
'<div class="stat-col">' . generate_stat_bar(user()->currenthp, user()->maxhp) . '<div>HP</div></div>' . '<div class="stat-col">' . generate_stat_bar((int)user()->currenthp, (int)user()->maxhp) . '<div>HP</div></div>' .
'<div class="stat-col">' . generate_stat_bar(user()->currentmp, user()->maxmp) . '<div>MP</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(user()->currenttp, user()->maxtp) . '<div>TP</div></div>' . '<div class="stat-col">' . generate_stat_bar((int)user()->currenttp, (int)user()->maxtp) . '<div>TP</div></div>' .
'</div>' . '</div>' .
'</div>'; '</div>';
@ -612,6 +583,23 @@ function create_stat_table($userrow)
*/ */
function user(): User|false function user(): User|false
{ {
$GLOBALS['state']['user'] ??= ($_SESSION['user_id'] ? User::find($_SESSION['user_id']) : false); $GLOBALS['state']['user'] ??= (isset($_SESSION['user_id']) ? User::find($_SESSION['user_id']) : false);
return $GLOBALS['state']['user']; 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';
}
/**
* Update the page title using HTMX.
*/
function htmx_update_page_title(string $new_title) {
header('HX-Trigger: ' . json_encode(['updateTitle' => ['title' => $new_title]]));
}

View File

@ -19,7 +19,11 @@ class Model
public function __set(string $key, mixed $value): void public function __set(string $key, mixed $value): void
{ {
if (array_key_exists($key, $this->original_data)) $this->changes[$key] = $value; if (array_key_exists($key, $this->original_data)) {
$this->changes[$key] = $value;
} else {
throw new InvalidArgumentException("Attempted to write to $key, which doesn't exist in the data for this model.");
}
} }
public function save(): bool public function save(): bool

View File

@ -18,4 +18,48 @@ class User extends Model
if ($data === false) return false; if ($data === false) return false;
return new User($data); return new User($data);
} }
/**
* Return a list of spells from this user's spell list.
*/
public function spells(): array|false
{
return get_spells_from_list($this->spells);
}
/**
* Restore all HP, MP, and TP values to their max.
*/
public function restore_points(): User
{
$this->currenthp = $this->maxhp;
$this->currentmp = $this->maxmp;
$this->currenttp = $this->maxtp;
return $this;
}
/**
* Save works just as it does on the Model class. In our case, though, user state changing may necessitate
* OOB swaps for parts of the UI that have user data displayed. Left and right nav, for example. In these cases,
* we set a flag in GLOBALS state to signify this.
*/
public function save(): bool
{
if (empty($this->changes)) return true;
$placeholders = [];
$values = [];
foreach ($this->changes as $key => $value) {
$placeholders[] = "$key=?";
$values[] = $value;
}
$values[] = $this->id;
$query = 'UPDATE ' . $this->table_name . ' SET ' . implode(', ', $placeholders) . ' WHERE id = ?;';
$result = db()->query($query, $values);
if ($result === false) return false;
$GLOBALS['state']['user-state-changed'] = true;
return true;
}
} }

40
src/render.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace Render;
/*
This file contains functions to render various UI elements. The goal is to begin shifting elements in the game
to HTMX/AJAX for more fluid gameplay.
*/
function header_links(): string
{
if (user() !== false) {
$links = "<a href='/logout'><img src='/img/button_logout.gif' alt='Log Out' title='Log Out'></a>";
} else {
$links = <<<HTML
<a href='/login'><img src='/img/button_login.gif' alt='Log In' title='Log In'></a>
<a href='/register'><img src='/img/button_register.gif' alt='Register' title='Register'></a>
HTML;
}
return $links .= "<a href='/help'><img src='/img/button_help.gif' alt='Help' title='Help'></a>";
}
function debug_db_info(): string {
$total_time = round(microtime(true) - START, 4);
return '<div id="debug-db-info" hx-swap-oob="true">'. $total_time . ' Seconds, ' . db()->count . ' Queries</div>';
}
function right_nav(): string
{
if (user() === false) return '';
// Flashy numbers if they're low
$hp = (user()->currenthp <= (user()->maxhp / 5)) ? "<blink><span class=\"highlight\"><b>*" . user()->currenthp . "*</b></span></blink>" : user()->currenthp;
$mp = (user()->currentmp <= (user()->maxmp / 5)) ? "<blink><span class=\"highlight\"><b>*" . user()->currentmp . "*</b></span></blink>" : user()->currentmp;
$template = render('right_nav', ['hp' => $hp, 'mp' => $mp]);
if (is_htmx()) $template = '<section id="right" hx-swap-oob="true">'.$template."</section>";
return $template;
}

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $title ?></title> <title><?= $title ?></title>
<link rel="stylesheet" href="/css/dk.css"> <link rel="stylesheet" href="/css/dk.css">
<script src="/js/htmx.js"></script>
<script> <script>
function opencharpopup(id = 0) function opencharpopup(id = 0)
@ -29,20 +30,20 @@
<main> <main>
<section id="left"><?= $leftnav ?></section> <section id="left"><?= $leftnav ?></section>
<section id="middle"><?= $content ?></section> <section id="middle"><?= $content ?></section>
<section id="right"><?= $rightnav ?></section> <section id="right"><?= Render\right_nav() ?></section>
</main> </main>
<footer> <footer>
<div>Powered by <a href="/" target="_new">Dragon Knight</a></div> <div>Powered by <a href="/" target="_new">Dragon Knight</a></div>
<div>&copy; 2024 Sharkk</div> <div>&copy; 2024 Sharkk</div>
<div><?= $totaltime ?> Seconds, <?= $numqueries ?> Queries</div> <?= Render\debug_db_info(); ?>
<div>Version <?= $version ?> <?= $build ?></div> <div>Version <?= VERSION ?> <?= BUILD ?></div>
</footer> </footer>
<?php <?php
if (!empty($querylog)) { if (env('debug', false)) {
echo '<pre>'; echo '<pre>';
foreach ($querylog 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] : '';
echo '<div>['.round($record[1], 2)."s] {$query_string}{$error_string}</div>"; echo '<div>['.round($record[1], 2)."s] {$query_string}{$error_string}</div>";
@ -51,5 +52,15 @@
} }
?> ?>
</div> </div>
<script>
document.addEventListener("updateTitle", (event) => {
const title = event.detail?.title;
if (title) {
console.log('New title:', title);
document.title = title;
}
});
</script>
</body> </body>
</html> </html>

View File

@ -2,26 +2,36 @@
<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><?= user()->username ?></b><br> <b><?= user()->username ?></b><br>
Level: <?= user()->level ?><br> Level: <?= user()->level ?><br>
Exp: <?= user()->experience ?><br> Exp: <?= number_format(user()->experience) ?><br>
Gold: <?= user()->gold ?><br> Gold: <?= number_format(user()->gold) ?><br>
HP: <?= user()->currenthp ?><br> HP: <?= $hp ?><br>
MP: <?= user()->currentmp ?><br> MP: <?= $mp ?><br>
TP: <?= user()->currenttp ?><br><br> TP: <?= user()->currenttp ?><br><br>
<?= $statbars ?><br> <?= 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"> Weapon: <?= $user['weaponname'] ?><br> <img src="/img/icon_weapon.gif" alt="Weapon" title="Weapon"> Weapon: <?= user()->weaponname ?><br>
<img src="/img/icon_armor.gif" alt="Armor" title="Armor"> Armor: <?= $user['armorname'] ?><br> <img src="/img/icon_armor.gif" alt="Armor" title="Armor"> Armor: <?= user()->armorname ?><br>
<img src="/img/icon_shield.gif" alt="Shield" title="Shield"> Shield: <?= $user['shieldname'] ?><br> <img src="/img/icon_shield.gif" alt="Shield" title="Shield"> Shield: <?= user()->shieldname ?><br>
Slot 1: <?= $user['slot1name'] ?><br> Slot 1: <?= user()->slot1name ?><br>
Slot 2: <?= $user['slot2name'] ?><br> Slot 2: <?= user()->slot2name ?><br>
Slot 3: <?= $user['slot3name'] ?> Slot 3: <?= 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>
<?= $user['magiclist'] ?> <?php
$user_spells = user()->spells();
if ($user_spells !== false) {
foreach ($user_spells as $spell) {
// list only healing spells for now
if ($spell['type'] === 1) echo "<a href=\"/spell/{$spell['id']}\">".$spell['name']."</a><br>";
}
} else {
echo 'None';
}
?>
</section> </section>

View File

@ -3,8 +3,8 @@
<div class="title"><img src="/img/town_<?= $town['id'] ?>.gif" alt="Welcome to <?= $town['name'] ?>" title="Welcome to <?= $town['name'] ?>"></div> <div class="title"><img src="/img/town_<?= $town['id'] ?>.gif" alt="Welcome to <?= $town['name'] ?>" title="Welcome to <?= $town['name'] ?>"></div>
<b>Town Options:</b><br> <b>Town Options:</b><br>
<ul> <ul>
<li><a href="/inn">Rest at the Inn</a></li> <li><a hx-get="/inn" hx-target="#middle">Rest at the Inn</a></li>
<li><a href="/buy">Buy Weapons/Armor</a></li> <li><a hx-get="/buy" hx-target="#middle">Buy Weapons/Armor</a></li>
<li><a href="/maps">Buy Maps</a></li> <li><a href="/maps">Buy Maps</a></li>
</ul> </ul>
</div> </div>