Part one of admin panel rework

This commit is contained in:
Sky Johnson 2024-12-19 18:33:17 -06:00
parent 8c667cbd2a
commit aca8bcc3fe
11 changed files with 430 additions and 221 deletions

View File

@ -13,7 +13,7 @@ show_babble = true
show_online = true
# Environment
debug = true
debug = false
# Email
smtp_host = smtp.foobar.com

View File

@ -1,5 +1,11 @@
:root {
--font-size: 12px;
--font-size: 16px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
@ -7,12 +13,26 @@ html {
font-family: sans-serif;
}
body {
padding: 2rem;
color: rgb(108, 108, 108);
background-color: rgb(245, 245, 245);
}
h1, h2, h3, h4, h5 {
color: rgb(30, 30, 30);
}
div#admin-container {
max-width: 1280px;
margin: 0 auto;
padding: 1rem;
}
header {
margin-bottom: 2rem;
}
main {
display: flex;
gap: 2rem;
@ -32,13 +52,16 @@ table {
}
a {
color: #663300;
color: #015df7;
text-decoration: none;
font-weight: bold;
}
a:hover {
color: #330000;
cursor: pointer;
&:hover {
color: hsl(218, 99%, 29%);
text-decoration: underline;
}
}
.small {
font: 10px verdana;
}
@ -59,8 +82,7 @@ a:hover {
footer {
display: flex;
justify-content: space-around;
border: solid 1px black;
background-color: #eeeeee;
font-size: 0.8rem;
padding: 0.5rem;
margin: 2rem 0;
}

View File

@ -17,7 +17,7 @@ $r->post('/move', 'Explore\move');
$r->get('/spell/:id', 'healspells');
$r->get('/character', 'show_character_info');
$r->get('/character/:id', 'show_character_info');
$r->get('/showmap', 'showmap');
$r->get('/showmap', 'show_map');
$r->form('/babblebox', 'babblebox');
$r->get('/babblebox/messages', 'babblebox_messages');
@ -40,19 +40,7 @@ $r->get('/ninja', function() {
$l = $r->lookup($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
if (is_int($l)) exit("Error: $l");
$content = $l['handler'](...$l['params'] ?? []);
if (is_htmx() && $uri[0] !== 'babblebox') {
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();
}
}
echo $content;
echo render_response($uri, $l['handler'](...$l['params'] ?? []));
exit;
/**
@ -96,7 +84,10 @@ function show_character_info(int $id = 0): string
return render('layouts/minimal', ['content' => $showchar, 'title' => $user->username.' Information']);
}
function showmap()
/**
* 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>',
@ -111,7 +102,7 @@ function showmap()
}
/**
* ...
* Handle a POST request to send a new babblebox message.
*/
function babblebox()
{

View File

@ -8,16 +8,16 @@ use Router;
function register_routes(Router $r): Router
{
if (user('authlevel') === 1) {
if (user()->authlevel === 1) {
$r->get('/admin', 'Admin\donothing');
$r->form('/admin/main', 'Admin\primary');
$r->get('/admin/items', 'Admin\items');
$r->form('/admin/items/:id', 'Admin\edititem');
$r->form('/admin/items/:id', 'Admin\edit_item');
$r->get('/admin/drops', 'Admin\drops');
$r->form('/admin/drops/:id', 'Admin\editdrop');
$r->form('/admin/drops/:id', 'Admin\edit_drop');
$r->get('/admin/towns', 'Admin\towns');
$r->form('/admin/towns/:id', 'Admin\edittown');
@ -39,18 +39,19 @@ function register_routes(Router $r): Router
return $r;
}
function donothing()
/**
* Home page for the admin panel.
*/
function donothing(): string
{
$page = <<<HTML
Welcome to the Dragon Knight 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
elements of the game.
<br><br>
Please note that the control panel has been created mostly as a shortcut for certain individual settings. It is
meant for use primarily with editing one thing at a time. If you need to completely replace an entire table
(say, to replace all stock monsters with your own new ones), it is suggested that you use a more in-depth
database tool such as <a href="http://www.phpmyadmin.net" target="_new">phpMyAdmin</a>. Also, you may want
to have a copy of the Dragon Knight development kit, available from the
<a href="http://dragon.se7enet.com/dev.php">Dragon Knight homepage</a>.
database tool such as <a href="https://sqlitebrowser.org/" target="_new">SQLite Browser</a>.
<br><br>
Also, you should be aware that certain portions of the DK code are dependent on the formatting of certain
database results (for example, the special attributes on item drops). While I have attempted to point these out
@ -58,18 +59,22 @@ function donothing()
because mistakes in the database content may result in script errors or your game breaking completely.
HTML;
display_admin($page, "Admin Home");
page_title('Admin');
return \Render\content($page, 'layouts/admin');
}
function primary()
/**
* Main settings that get written to .env
*/
function primary(): string
{
if (isset($_POST["submit"])) {
if (is_post()) {
$form = validate($_POST, [
'gamename' => ['alphanum-spaces', 'length:1-20'],
'gamename' => ['alphanum-spaces'],
'gamesize' => ['int', 'min:5'],
'class1name' => ['alpha-spaces', 'length:1-18'],
'class2name' => ['alpha-spaces', 'length:1-18'],
'class3name' => ['alpha-spaces', 'length:1-18'],
'class1name' => ['alpha-spaces'],
'class2name' => ['alpha-spaces'],
'class3name' => ['alpha-spaces'],
'gameopen' => ['bool'],
'verifyemail' => ['bool'],
'shownews' => ['bool'],
@ -81,203 +86,153 @@ function primary()
$form = $form['data'];
if (($form['gamesize'] % 5) != 0) exit('Map size must be divisible by five.');
db()->query('UPDATE control SET gamename=?, gamesize=?, class1name=?, class2name=?, class3name=?, gameopen=?, verifyemail=?, gameurl=?, adminemail=?, shownews=?, showonline=?, showbabble=? WHERE id=1;', [
$form['gamename'], $form['gamesize'], $form['class1name'], $form['class2name'], $form['class3name'], $form['gameopen'], $form['verifyemail'], $form['gameurl'], $form['adminemail'], $form['shownews'], $form['showonline'], $form['showbabble']
]);
// @todo
// write changes to .env
display_admin("Settings updated.", "Main Settings");
$page = 'Main settings updated.';
} else {
$errorlist = ul_from_validate_errors($form['errors']);
display_admin("<b>Errors:</b><br><div style=\"color:red;\">$errorlist</div><br>Please go back and try again.", "Main Settings");
$error_list = ul_from_validate_errors($form['errors']);
$page = <<<HTML
<b>Errors:</b><br>
<div style="color: red;">{$error_list}</div><br>
Please go back and try again.
HTML;
}
}
} else {
$page = render('admin/main_settings');
}
global $controlrow;
$page = <<<HTML
<b><u>Main Settings</u></b><br>
These options control several major settings for the overall game engine.<br><br>
<form action="/admin/main" method="post">
<table width="90%">
<tr><td width="20%"><span class="highlight">Game Open:</span></td><td><select name="gameopen"><option value="1" {{open1select}}>Open</option><option value="0" {{open0select}}>Closed</option></select><br><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></td></tr>
<tr><td width="20%">Game Name:</td><td><input type="text" name="gamename" value="{{gamename}}" /><br><span class="small">Default is "Dragon Knight". Change this if you want to change to call your game something different.</span></td></tr>
<tr><td width="20%">Game URL:</td><td><input type="text" name="gameurl" value="{{gameurl}}" /><br><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 you leave this field blank or incorrect, users may not be able to register correctly.</span></td></tr>
<tr><td width="20%">Admin Email:</td><td><input type="text" name="adminemail" value="{{adminemail}}" /><br><span class="small">Please specify your email address. This gets used when the game has to send an email to users.</span></td></tr>
<tr><td width="20%">Map Size:</td><td><input type="text" name="gamesize" value="{{gamesize}}" /><br><span class="small">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) monster levels total, otherwise there will be parts of the map without any monsters, or some monsters won't ever get used. Ex: with a map size of 250, you should have 50 monster levels total.</span></td></tr>
<tr><td width="20%">Email Verification:</td><td><select name="verifyemail"><option value="0" {{selectverify0}}>Disabled</option><option value="1" {{selectverify1}}>Enabled</option></select><br><span class="small">Make users verify their email address for added security.</span></td></tr>
<tr><td width="20%">Show News:</td><td><select name="shownews"><option value="0" {{selectnews0}}>No</option><option value="1" {{selectnews1}}>Yes</option></select><br><span class="small">Toggle display of the Latest News box in towns.</td></tr>
<tr><td width="20%">Show Who's Online:</td><td><select name="showonline"><option value="0" {{selectonline0}}>No</option><option value="1" {{selectonline1}}>Yes</option></select><br><span class="small">Toggle display of the Who's Online box in towns.</span></td></tr>
<tr><td width="20%">Show Babblebox:</td><td><select name="showbabble"><option value="0" {{selectbabble0}}>No</option><option value="1" {{selectbabble1}}>Yes</option></select><br><span class="small">Toggle display of the Babble Box in towns.</span></td></tr>
<tr><td width="20%">Class 1 Name:</td><td><input type="text" name="class1name" value="{{class1name}}" /><br></td></tr>
<tr><td width="20%">Class 2 Name:</td><td><input type="text" name="class2name" value="{{class2name}}" /><br></td></tr>
<tr><td width="20%">Class 3 Name:</td><td><input type="text" name="class3name" value="{{class3name}}" /><br></td></tr>
</table>
<input type="submit" name="submit" value="Submit"> <input type="reset" name="reset" value="Reset">
</form>
HTML;
if ($controlrow["verifyemail"] == 0) { $controlrow["selectverify0"] = "selected=\"selected\" "; } else { $controlrow["selectverify0"] = ""; }
if ($controlrow["verifyemail"] == 1) { $controlrow["selectverify1"] = "selected=\"selected\" "; } else { $controlrow["selectverify1"] = ""; }
if ($controlrow["shownews"] == 0) { $controlrow["selectnews0"] = "selected=\"selected\" "; } else { $controlrow["selectnews0"] = ""; }
if ($controlrow["shownews"] == 1) { $controlrow["selectnews1"] = "selected=\"selected\" "; } else { $controlrow["selectnews1"] = ""; }
if ($controlrow["showonline"] == 0) { $controlrow["selectonline0"] = "selected=\"selected\" "; } else { $controlrow["selectonline0"] = ""; }
if ($controlrow["showonline"] == 1) { $controlrow["selectonline1"] = "selected=\"selected\" "; } else { $controlrow["selectonline1"] = ""; }
if ($controlrow["showbabble"] == 0) { $controlrow["selectbabble0"] = "selected=\"selected\" "; } else { $controlrow["selectbabble0"] = ""; }
if ($controlrow["showbabble"] == 1) { $controlrow["selectbabble1"] = "selected=\"selected\" "; } else { $controlrow["selectbabble1"] = ""; }
if ($controlrow["gameopen"] == 1) { $controlrow["open1select"] = "selected=\"selected\" "; } else { $controlrow["open1select"] = ""; }
if ($controlrow["gameopen"] == 0) { $controlrow["open0select"] = "selected=\"selected\" "; } else { $controlrow["open0select"] = ""; }
display_admin(parse($page, $controlrow), "Main Settings");
page_title('Admin: Main Settings');
return \Render\content($page, 'layouts/admin');
}
function items()
/**
* Show the full list of items that can be edited.
*/
function items(): string
{
$items = db()->query('SELECT id, name FROM items ORDER BY id;');
$page = "<b><u>Edit Items</u></b><br>Click an item's name to edit it.<br><br><table width=\"50%\">\n";
$page = "<h2>Edit Items</h2>Click an item's name to edit it.<br><br><table>\n";
$hasItems = false;
while ($row = $items->fetchArray(SQLITE3_BOTH)) {
$hasItems = true;
$page .= "<tr><td width=\"8%\" style=\"background-color: #eeeeee;\">".$row["id"]."</td><td style=\"background-color: #eeeeee;\"><a href=\"/admin/items/".$row["id"]."\">".$row["name"]."</a></td></tr>\n";
$page .= <<<HTML
<tr>
<td width="8%" style="background-color: #eeeeee;">{$row["id"]}</td>
<td style="background-color: #eeeeee;"><a href="/admin/items/{$row['id']}" hx-get="/admin/items/{$row['id']}" hx-target="#main">{$row["name"]}</a></td>
</tr>
HTML;
}
if (!$hasItems) $page .= "<tr><td width=\"8%\" style=\"background-color: #eeeeee;\">No items found.</td></tr>\n";
display_admin($page . "</table>", "Edit Items");
page_title('Admin: Items');
return \Render\content($page . '</table>', 'layouts/admin');
}
function edititem($id)
/**
* Shows the form for editing an item via GET, processes edits via POST
*/
function edit_item(int $id): string
{
if (isset($_POST["submit"])) {
$errors = [];
$n = trim($_POST['name'] ?? '');
$bc = (int) trim($_POST['buycost'] ?? 0);
$a = (int) trim($_POST['attribute'] ?? 0);
$s = trim($_POST['special'] ?? 'X');
if (empty($n)) $errors[] = "Name is required.";
if (!is_int($bc) || !($bc >= 0)) $errors[] = 'Cost must be a number greater than or equal to 0.';
if (!is_int($a)) $errors[] = 'Attribute must be a number.';
if (count($errors) === 0) {
db()->query('UPDATE items SET name=?, type=?, buycost=?, attribute=?, special=? WHERE id=?;', [
$n, $_POST['type'] ?? 0, $bc, $a, $s, $id
]);
display_admin("Item updated.","Edit Items");
} else {
$errorlist = implode('<br>', $errors);
display_admin("<b>Errors:</b><br><div style=\"color:red;\">$errorlist</div><br>Please go back and try again.", "Edit Items");
}
}
$item = get_item($id);
$page = <<<HTML
<b><u>Edit Items</u></b><br><br>
<form action="/admin/items/$id" method="post">
<table width="90%">
<tr><td width="20%">ID:</td><td>{{id}}</td></tr>
<tr><td width="20%">Name:</td><td><input type="text" name="name" value="{{name}}" /></td></tr>
<tr><td width="20%">Type:</td><td><select name="type"><option value="1" {{type1select}}>Weapon</option><option value="2" {{type2select}}>Armor</option><option value="3" {{type3select}}>Shield</option></select></td></tr>
<tr><td width="20%">Cost:</td><td><input type="text" name="buycost" value="{{buycost}}" /> gold</td></tr>
<tr><td width="20%">Attribute:</td><td><input type="text" name="attribute" value="{{attribute}}" /><br><span class="small">How much the item adds to total attackpower (weapons) or defensepower (armor/shields).</span></td></tr>
<tr><td width="20%">Special:</td><td><input type="text" name="special" value="{{special}}" /><br><span class="small">Should be either a special code or <span class="highlight">X</span> to disable. Edit this field very carefully because mistakes to formatting or field names can create problems in the game.</span></td></tr>
</table>
<input type="submit" name="submit" value="Submit" /> <input type="reset" name="reset" value="Reset" />
</form>
<b>Special Codes:</b><br>
Special codes can be added in the item's Special field to give it extra user attributes. Special codes are in the format <span class="highlight">attribute,value</span>. <span class="highlight">Attribute</span> can be any database field from the Users table - however, it is suggested that you only use the ones from the list below, otherwise things can get freaky. <span class="highlight">Value</span> may be any positive or negative whole number. For example, if you want a weapon to give an additional 50 max hit points, the special code would be <span class="highlight">maxhp,50</span>.<br><br>
Suggested user fields for special codes:<br>
maxhp - max hit points<br>
maxmp - max magic points<br>
maxtp - max travel points<br>
goldbonus - gold bonus, in percent<br>
expbonus - experience bonus, in percent<br>
strength - strength (which also adds to attackpower)<br>
dexterity - dexterity (which also adds to defensepower)<br>
attackpower - total attack power<br>
defensepower - total defense power
HTML;
if (is_post()) {
$form = validate($_POST, [
'name' => [],
'type' => ['int', 'in:1,2,3'],
'buycost' => ['int', 'min:0'],
'attribute' => ['int', 'min:0'],
'special' => ['default:X']
]);
if ($item["type"] == 1) { $item["type1select"] = "selected=\"selected\" "; } else { $item["type1select"] = ""; }
if ($item["type"] == 2) { $item["type2select"] = "selected=\"selected\" "; } else { $item["type2select"] = ""; }
if ($item["type"] == 3) { $item["type3select"] = "selected=\"selected\" "; } else { $item["type3select"] = ""; }
if ($form['valid']) {
$f = $form['data'];
db()->query('UPDATE items SET name=?, type=?, buycost=?, attribute=?, special=? WHERE id=?;', [
$f['name'], $f['type'], $f['buycost'], $f['attribute'], $f['special'], $id
]);
$page = '<b>'.$item['name'].'</b> updated.';
} else {
$error_list = ul_from_validate_errors($form['errors']);
$page = <<<HTML
<b>Errors:</b><br>
<div style="color: red;">{$error_list}</div><br>
Please go back and try again.
HTML;
}
} else {
$page = render('admin/edit_item', ['item' => $item]);
}
display_admin(parse($page, $item), "Edit Items");
page_title('Admin: Editing '.$item['name']);
return \Render\content($page, 'layouts/admin');
}
/**
* Show the full list of drops that can be edited
*/
function drops()
{
$page = "<b><u>Edit Drops</u></b><br>Click an item's name to edit it.<br><br><table width=\"50%\">\n";
$page = "<h2>Edit Drops</h2>Click an item's name to edit it.<br><br><table>\n";
$drops = db()->query('SELECT id, name FROM drops ORDER BY id;');
$has_drops = false;
while ($row = $drops->fetchArray(SQLITE3_ASSOC)) {
$has_drops = true;
$page .= "<tr><td width=\"8%\" style=\"background-color: #eeeeee;\">".$row["id"]."</td><td style=\"background-color: #eeeeee;\"><a href=\"/admin/drops/".$row["id"]."\">".$row["name"]."</a></td></tr>\n";
$page .= <<<HTML
<tr>
<td width="8%" style="background-color: #eeeeee;">{$row["id"]}</td>
<td style="background-color: #eeeeee;"><a href="/admin/drops/{$row['id']}" hx-get="/admin/drops/{$row['id']}" hx-target="#main">{$row["name"]}</a></td>
</tr>
HTML;
}
if (!$has_drops) { $page .= "<tr><td width=\"8%\" style=\"background-color: #eeeeee;\">No drops found.</td></tr>\n"; }
display_admin($page . "</table>", "Edit Drops");
page_title('Admin: Drops');
return \Render\content($page . '</table>', 'layouts/admin');
}
function editdrop($id)
/**
* Show the form to edit drops via GET, process those edits via POST
*/
function edit_drop(int $id): string
{
if (isset($_POST["submit"])) {
$errors = [];
$n = trim($_POST['name'] ?? '');
$ml = (int) trim($_POST['mlevel'] ?? 0);
$a = trim($_POST['attribute1'] ?? 'X');
$a2 = trim($_POST['attribute2'] ?? 'X');
if (empty($n)) $errors[] = "Name is required.";
if (!is_int($ml) || $ml < 1) $errors[] = "Monster level is required, and must be higher than 0.";
if (empty($a) || $a === 'X') $errors[] = 'First attribute is required.';
if (empty($a2)) $a2 = 'X';
if (count($errors) === 0) {
db()->query('UPDATE drops SET name=?, mlevel=?, attribute1=?, attribute2=? WHERE id=?;', [
$n, $ml, $a, $a2, $id
]);
display_admin("Item updated.","Edit Drops");
} else {
$errorlist = implode('<br>', $errors);
display_admin("<b>Errors:</b><br><div style=\"color:red;\">$errorlist</div><br>Please go back and try again.", "Edit Drops");
}
}
$drop = get_drop($id);
$page = <<<HTML
<b><u>Edit Drops</u></b><br><br>
<form action="/admin/drops/$id" method="post">
<table width="90%">
<tr><td width="20%">ID:</td><td>{{id}}</td></tr>
<tr><td width="20%">Name:</td><td><input type="text" name="name" value="{{name}}" /></td></tr>
<tr><td width="20%">Monster Level:</td><td><input type="number" name="mlevel" value="{{mlevel}}" /><br><span class="small">Minimum monster level that will drop this item.</span></td></tr>
<tr><td width="20%">Attribute 1:</td><td><input type="text" name="attribute1" value="{{attribute1}}" /><br><span class="small">Must be a special code. First attribute cannot be disabled. Edit this field very carefully because mistakes to formatting or field names can create problems in the game.</span></td></tr>
<tr><td width="20%">Attribute 2:</td><td><input type="text" name="attribute2" value="{{attribute2}}" /><br><span class="small">Should be either a special code or <span class="highlight">X</span> to disable. Edit this field very carefully because mistakes to formatting or field names can create problems in the game.</span></td></tr>
</table>
<input type="submit" name="submit" value="Submit" /> <input type="reset" name="reset" value="Reset" />
</form>
<b>Special Codes:</b><br>
Special codes are used in the two attribute fields to give the item properties. The first attribute field must contain a special code, but the second one may be left empty ("X") if you wish. Special codes are in the format <span class="highlight">attribute,value</span>. <span class="highlight">Attribute</span> can be any database field from the Users table - however, it is suggested that you only use the ones from the list below, otherwise things can get freaky. <span class="highlight">Value</span> may be any positive or negative whole number. For example, if you want a weapon to give an additional 50 max hit points, the special code would be <span class="highlight">maxhp,50</span>.<br><br>
Suggested user fields for special codes:<br>
maxhp - max hit points<br>
maxmp - max magic points<br>
maxtp - max travel points<br>
goldbonus - gold bonus, in percent<br>
expbonus - experience bonus, in percent<br>
strength - strength (which also adds to attackpower)<br>
dexterity - dexterity (which also adds to defensepower)<br>
attackpower - total attack power<br>
defensepower - total defense power
HTML;
if (is_post()) {
$form = validate($_POST, [
'name' => [],
'mlevel' => ['int', 'min:1'],
'attribute1' => [],
'attribute2' => ['default:X'],
]);
display_admin(parse($page, $drop), "Edit Drops");
if ($form['valid']) {
db()->query('UPDATE drops SET name=?, mlevel=?, attribute1=?, attribute2=? WHERE id=?;', [
$form['data']['name'], $form['data']['mlevel'], $form['data']['attribute1'], $form['data']['attribute2'], $id
]);
$page = '<b>'.$form['data']['name'].'</b> updated.';
} else {
$error_list = ul_from_validate_errors($form['errors']);
$page = <<<HTML
<b>Errors:</b><br>
<div style="color: red;">{$error_list}</div><br>
Please go back and try again.
HTML;
}
} else {
$page = render('admin/edit_drop', ['drop' => $drop]);
}
page_title('Admin: Editing '.$drop['name']);
return \Render\content($page, 'layouts/admin');
}
/**
* Generate the list of towns that can be edited.
*/
function towns()
{
$page = "<b><u>Edit Towns</u></b><br>Click an town's name to edit it.<br><br><table width=\"50%\">\n";

View File

@ -82,11 +82,7 @@ function display_admin($content, $title)
{
echo render('layouts/admin', [
"title" => $title,
"content" => $content,
"totaltime" => round(microtime(true) - START, 4),
"numqueries" => db()->count,
"version" => VERSION,
"build" => BUILD
"content" => $content
]);
exit;
@ -511,3 +507,29 @@ 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 (!is_htmx() || $uri[0] === 'babblebox') return $content;
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 $content;
}

View File

@ -43,6 +43,7 @@ class User extends Model
*/
public function update_online_time(): void
{
if ($this->onlinetime && strtotime($this->onlinetime) > strtotime('-9 minutes')) return;
db()->query('UPDATE users SET onlinetime=CURRENT_TIMESTAMP WHERE id=?;', [$this->id]);
}

View File

@ -9,12 +9,12 @@ namespace Render;
/**
* Prepare content for final render. If the request is HTMX-based, will return just the content passed to it. Otherwise
* it will render() onto layouts/primary with some additional bits.
* it will render() onto $layout with some additional bits.
*/
function content(string $content): string
function content(string $content, string $layout = 'layouts/primary'): string
{
if (is_htmx()) return $content;
return render('layouts/primary', ['content' => $content]);
return render($layout, ['content' => $content]);
}
function debug_db_info(): string {

View File

@ -0,0 +1,49 @@
<h2>Editing <?= $drop['name'] ?></h2>
<form action="/admin/drops/<?= $drop['id'] ?>" method="post" hx-post="/admin/drops/<?= $drop['id'] ?>" hx-target="#main">
<table width="90%">
<tr><td width="20%">ID:</td><td><?= $drop['id'] ?></td></tr>
<tr><td width="20%">Name:</td><td><input type="text" name="name" value="<?= $drop['name'] ?>"></td></tr>
<tr>
<td width="20%">Monster Level:</td>
<td>
<input type="number" name="mlevel" value="<?= $drop['mlevel'] ?>"><br>
<span class="small">Minimum monster level that will drop this item.</span>
</td>
</tr>
<tr>
<td width="20%">Attribute 1:</td>
<td>
<input type="text" name="attribute1" value="<?= $drop['attribute1'] ?>"><br>
<span class="small">Must be a special code. First attribute cannot be disabled. Edit this field very
carefully because mistakes to formatting or field names can create problems in the game.</span>
</td>
</tr>
<tr>
<td width="20%">Attribute 2:</td>
<td>
<input type="text" name="attribute2" value="<?= $drop['attribute2'] ?>"><br>
<span class="small">Should be either a special code or <span class="highlight">X</span> to
disable. Edit this field very carefully because mistakes to formatting or field names can create
problems in the game.</span>
</td>
</tr>
</table>
<button type="submit">Save</button>
<button type="reset">Reset</button>
</form>
<br>
<h3>Special Codes</h3>
Special codes are used in the two attribute fields to give the item properties. The first attribute field must contain a special code, but the second one may be left empty ("X") if you wish. Special codes are in the format <span class="highlight">attribute,value</span>. <span class="highlight">Attribute</span> can be any database field from the Users table - however, it is suggested that you only use the ones from the list below, otherwise things can get freaky. <span class="highlight">Value</span> may be any positive or negative whole number. For example, if you want a weapon to give an additional 50 max hit points, the special code would be <span class="highlight">maxhp,50</span>.<br><br>
Suggested user fields for special codes:<br>
maxhp - max hit points<br>
maxmp - max magic points<br>
maxtp - max travel points<br>
goldbonus - gold bonus, in percent<br>
expbonus - experience bonus, in percent<br>
strength - strength (which also adds to attackpower)<br>
dexterity - dexterity (which also adds to defensepower)<br>
attackpower - total attack power<br>
defensepower - total defense power

View File

@ -0,0 +1,55 @@
<h2>Edit <?= $item['name'] ?></h2>
<form action="/admin/items/<?= $item['id'] ?>" method="post" hx-post="/admin/items/<?= $item['id'] ?>" hx-target="#main">
<table>
<tr><td width="20%">ID:</td><td><?= $item['id'] ?></td></tr>
<tr>
<td width="20%">Name:</td>
<td><input type="text" name="name" value="<?= $item['name'] ?>"></td>
</tr>
<tr>
<td width="20%">Type:</td>
<td><select name="type">
<option value="1" <?= $item['type'] === 1 ? 'selected' : '' ?>>Weapon</option>
<option value="2" <?= $item['type'] === 2 ? 'selected' : '' ?>>Armor</option>
<option value="3" <?= $item['type'] === 3 ? 'selected' : '' ?>>Shield</option>
</select></td>
</tr>
<tr>
<td width="20%">Cost:</td>
<td><input type="text" name="buycost" value="<?= $item['buycost'] ?>"> gold</td>
</tr>
<tr>
<td width="20%">Attribute:</td>
<td>
<input type="number" name="attribute" value="<?= $item['attribute'] ?>"><br>
<span class="small">How much the item adds to total attackpower (weapons) or defensepower (armor/shields).</span>
</td>
</tr>
<tr>
<td width="20%">Special:</td>
<td>
<input type="text" name="special" value="<?= $item['special'] ?>"><br>
<span class="small">Should be either a special code or <span class="highlight">X</span> to disable. Edit
this field very carefully because mistakes to formatting or field names can create problems in the game.</span>
</td>
</tr>
</table>
<button type="submit">Save</button>
<button type="reset">Reset</button>
</form>
<br>
<h3>Special Codes</h3>
Special codes can be added in the item's Special field to give it extra user attributes. Special codes are in the format <span class="highlight">attribute,value</span>. <span class="highlight">Attribute</span> can be any database field from the Users table - however, it is suggested that you only use the ones from the list below, otherwise things can get freaky. <span class="highlight">Value</span> may be any positive or negative whole number. For example, if you want a weapon to give an additional 50 max hit points, the special code would be <span class="highlight">maxhp,50</span>.<br><br>
Suggested user fields for special codes:<br>
maxhp - max hit points<br>
maxmp - max magic points<br>
maxtp - max travel points<br>
goldbonus - gold bonus, in percent<br>
expbonus - experience bonus, in percent<br>
strength - strength (which also adds to attackpower)<br>
dexterity - dexterity (which also adds to defensepower)<br>
attackpower - total attack power<br>
defensepower - total defense power

View File

@ -0,0 +1,110 @@
<h2>Main Settings</h2>
<p>These options control several major settings for the game engine.</p>
<p>Note that these particular settings are written to the .env file in the root directory, and not the database.</p>
<br>
<form action="/admin/main" method="post" hx-post="/admin/main" hx-target="#main">
<table>
<tr>
<td width="20%"><span class="highlight">Game Open:</span></td>
<td>
<select name="gameopen">
<option value="1" <?= env('game_open') ? 'selected' : '' ?>>Open</option>
<option value="0" <?= !env('game_open') ? 'selected' : '' ?>>Closed</option>
</select><br>
<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>
</td>
</tr>
<tr>
<td width="20%">Game Name:</td>
<td>
<input type="text" name="gamename" value="<?= env('game_name') ?>"><br>
<span class="small">Change this if you want to change to call your game something different.</span>
</td>
</tr>
<tr>
<td width="20%">Game URL:</td>
<td>
<input type="text" name="gameurl" value="<?= env('game_url') ?>"><br>
<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
you leave this field blank or incorrect, users may not be able to register correctly.</span>
</td>
</tr>
<tr>
<td width="20%">Admin Email:</td>
<td>
<input type="text" name="adminemail" value="<?= env('admin_email') ?>"><br>
<span class="small">Please specify your email address. This gets used when the game has to send an
email to users.</span>
</td>
</tr>
<tr>
<td width="20%">Map Size:</td>
<td>
<input type="number" name="gamesize" value="<?= env('game_size') ?>"><br>
<span class="small">
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)
monster levels total, otherwise there will be parts of the map without any monsters, or some
monsters won't ever get used. Ex: with a map size of 250, you should have 50 monster levels total.
</span>
</td>
</tr>
<tr>
<td width="20%">Email Verification:</td>
<td>
<select name="verifyemail">
<option value="0" <?= !env('verify_email') ? 'selected' : '' ?>>Disabled</option>
<option value="1" <?= env('verify_email') ? 'selected' : '' ?>>Enabled</option>
</select><br>
<span class="small">Make users verify their email address for added security.</span>
</td>
</tr>
<tr>
<td width="20%">Show News:</td>
<td>
<select name="shownews">
<option value="0" <?= !env('show_news') ? 'selected' : '' ?>>No</option>
<option value="1" <?= env('show_news') ? 'selected' : '' ?>>Yes</option>
</select><br>
<span class="small">Toggle display of the Latest News box in towns.
</td>
</tr>
<tr>
<td width="20%">Show Who's Online:</td>
<td>
<select name="showonline">
<option value="0" <?= !env('show_online') ? 'selected' : '' ?>>No</option>
<option value="1" <?= env('show_online') ? 'selected' : '' ?>>Yes</option>
</select><br>
<span class="small">Toggle display of the Who's Online box in towns.</span>
</td>
</tr>
<tr>
<td width="20%">Show Babblebox:</td>
<td>
<select name="showbabble">
<option value="0" <?= !env('show_babble') ? 'selected' : '' ?>>No</option>
<option value="1" <?= env('show_babble') ? 'selected' : '' ?>>Yes</option>
</select><br>
<span class="small">Toggle display of the Babble Box in towns.</span>
</td>
</tr>
<tr>
<td width="20%">Class 1 Name:</td>
<td><input type="text" name="class1name" value="<?= env('class_1_name') ?>"><br></td>
</tr>
<tr>
<td width="20%">Class 2 Name:</td>
<td><input type="text" name="class2name" value="<?= env('class_2_name') ?>"><br></td>
</tr>
<tr>
<td width="20%">Class 3 Name:</td>
<td><input type="text" name="class3name" value="<?= env('class_3_name') ?>"><br></td>
</tr>
</table>
<button type="submit">Save</button>
<button type="reset">Reset</button>
</form>

View File

@ -3,34 +3,36 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $title ?></title>
<title><?= page_title() ?></title>
<link rel="stylesheet" href="/css/admin.css">
<script src="/js/htmx.js"></script>
</head>
<body>
<div id="admin-container">
<header>
<h1><?= env('game_name') ?></h1>
<h3>Admin</h3>
</header>
<main>
<nav>
<h1>Admin Panel</h1>
<b>Links:</b><br>
<a href="/admin">Admin Home</a><br>
<a href="/admin" hx-get="/admin" hx-target="#main">Admin Home</a><br>
<a href="/">Game Home</a><br><br>
<b>Primary Data:</b><br>
<a href="/admin/main">Main Settings</a><br>
<a href="/admin/news">Add News Post</a><br>
<a href="/admin/users">Edit Users</a><br><br>
<br>
<a href="/admin/main" hx-get="/admin/main" hx-target="#main">Main Settings</a><br>
<a href="/admin/news" hx-get="/admin/news" hx-target="#main">Add News Post</a><br>
<a href="/admin/users" hx-get="/admin/users" hx-target="#main">Edit Users</a><br><br>
<b>Game Data:</b><br>
<a href="/admin/items">Edit Items</a><br>
<a href="/admin/drops">Edit Drops</a><br>
<a href="/admin/towns">Edit Towns</a><br>
<a href="/admin/monsters">Edit Monsters</a><br>
<a href="/admin/level">Edit Levels</a><br>
<a href="/admin/spells">Edit Spells</a><br>
<br>
<a href="/admin/items" hx-get="/admin/items" hx-target="#main">Edit Items</a><br>
<a href="/admin/drops" hx-get="/admin/drops" hx-target="#main">Edit Drops</a><br>
<a href="/admin/towns" hx-get="/admin/towns" hx-target="#main">Edit Towns</a><br>
<a href="/admin/monsters" hx-get="/admin/monsters" hx-target="#main">Edit Monsters</a><br>
<a href="/admin/level" hx-get="/admin/level" hx-target="#main">Edit Levels</a><br>
<a href="/admin/spells" hx-get="/admin/spells" hx-target="#main">Edit Spells</a><br>
</nav>
<section>
<section id="main">
<?= $content ?>
</section>
</main>
@ -38,9 +40,11 @@
<footer>
<div>Powered by <a href="/" target="_new">Dragon Knight</a></div>
<div>&copy; 2024 Sharkk</div>
<div><?= $totaltime ?> Seconds, <?= $numqueries ?> Queries</div>
<div>Version <?= $version ?> <?= $build ?></div>
<?= Render\debug_db_info(); ?>
<div>Version <?= VERSION ?> <?= BUILD ?></div>
</footer>
<?php if (env('debug', false)) echo Render\debug_query_log(); ?>
</div>
</body>
</html>