diff --git a/public/admin.php b/public/admin.php
index d2d1a7b..32dbb86 100644
--- a/public/admin.php
+++ b/public/admin.php
@@ -11,7 +11,7 @@ $controlrow = get_control_row();
// Login (or verify) if not logged in.
if (($userrow = checkcookies()) === false) {
if (isset($_GET['do']) && $_GET['do'] === 'verify') redirect('users.php?do=verify');
- redirect('login.php?do=login');
+ redirect('/login');
}
if ($userrow["authlevel"] !== 1) exit("You must have administrator privileges to use the control panel.");
diff --git a/public/forum.php b/public/forum.php
index 1c7f1aa..976408a 100644
--- a/public/forum.php
+++ b/public/forum.php
@@ -15,7 +15,7 @@ if (($userrow = checkcookies()) === false) {
exit;
}
- header("Location: login.php?do=login");
+ header("Location: /login");
exit;
}
diff --git a/public/help.php b/public/help.php
deleted file mode 100644
index 27591f3..0000000
--- a/public/help.php
+++ /dev/null
@@ -1,568 +0,0 @@
-
-
-
-
-
- = $controlrow["gamename"] ?> Help
-
-
-
-
- = $controlrow["gamename"] ?> Help
- [ Return to the game ]
-
-
-
-
- Table of Contents
-
-
-
-
- Introduction
- Firstly, I'd like to say thank you for playing my game. The Dragon Knight game engine is the result of several months of
- planning, coding and testing. The original idea was to create a web-based tribute to the NES game, Dragon
- Warrior. In its current iteration, only the underlying fighting system really resembles that game, as almost
- everything else in DK has been made bigger and better. But you should still recognize bits and pieces as stemming
- from Dragon Warrior and other RPGs of old.
- This is the first game I've ever written, and it has definitely been a positive experience. It got difficult at
- times, admittedly, but it was still a lot of fun to write, and even more fun to play. And I hope to use this
- experience so that if I ever want to create another game it will be even better than this one.
- If you are a site administrator, and would like to install a copy of DK on your own server, you may visit the
- development site for Dragon Knight. This page
- includes the downloadable game souce code, as well as some other resources that developers and administrators may
- find valuable.
- Once again, thanks for playing!
- Jamin Seven
- Dragon Knight creator
- My Homepage
- Dragon Knight Homepage
- [ Top ]
-
-
-
- Character Classes
- There are three character classes in the game. The main differences between the classes are what spells you get
- access to, the speed with which you level up, and the amount of HP/MP/strength/dexterity you gain per level. Below
- is a basic outline of each of the character classes. For more detailed information about the characters, please
- view the Levels table at the bottom of this page. Also, note that the outline below refers to the stock class setup
- for the game. If your administrator has used his/her own class setup, this information may not be accurate.
- = $controlrow["class1name"] ?>
-
- - Fast level-ups
- - High hit points
- - High magic points
- - Low strength
- - Low dexterity
- - 5 heal spells
- - 5 hurt spells
- - 3 sleep spells
- - 3 +defense spells
- - 0 +attack spells
-
- = $controlrow["class2name"] ?>
-
- - Medium level-ups
- - Medium hit points
- - Low magic points
- - High strength
- - Low dexterity
- - 3 heal spells
- - 3 hurt spells
- - 2 sleep spells
- - 3 +defense spells
- - 3 +attack spells
-
- = $controlrow["class3name"] ?>
-
- - Slow level-ups
- - Medium hit points
- - Medium magic points
- - Low strength
- - High dexterity
- - 4 heal spells
- - 4 hurt spells
- - 3 sleep spells
- - 2 +defense spells
- - 2 +attack spells
-
- [ Top ]
-
-
-
- Playing The Game: In Town
- When you begin a new game, the first thing you see is the Town screen. Towns serve four primary functions: healing, buying items,
- buying maps, and displaying game information.
- To heal yourself, click the "Rest at the Inn" link at the top of the town screen. Each town's Inn has a different price - some towns
- are cheap, others are expensive. No matter what town you're in, the Inns always serve the same function: they restore your current
- hit points, magic points, and travel points to their maximum amounts. Out in the field, you are free to use healing spells to restore
- your hit points, but when you run low on magic points, the only way to restore them is at an Inn.
- Buying weapons and armor is accomplished through the appropriately-named "Buy Weapons/Armor" link. Not every item is available in
- every town, so in order to get the most powerful items, you'll need to explore some of the outer towns. Once you've clicked the link,
- you are presented with a list of items available in this town's store. To the left of each item is an icon that represents its type:
- weapon, armor or shield. The amount of attack/defense power, as well as the item's price, are displayed to the right of the item name.
- You'll notice that some items have a red asterisk (*) next to their names. These are items that come
- with special attributes that modify other parts of your character profile. See the Items & Drops table at the bottom of this page for
- more information about special items.
- Maps are the third function in towns. Buying a map to a town places the town in your Travel To box in the left status panel. Once
- you've purchased a town's map, you can click its name from your Travel To box and you will jump to that town. Travelling this way
- costs travel points, though, and you'll only be able to visit towns if you have enough travel points.
- The final function in towns is displaying game information and statistics. This includes the latest news post made by the game
- administrator, a list of players who have been online recently, and the Babble Box.
- [ Top ]
-
-
-
- Playing The Game: Exploring & Fighting
- Once you're done in town, you are free to start exploring the world. Use the compass buttons on the left status panel to move around.
- The game world is basically a big square, divided into four quadrants. Each quadrant is = $controlrow["gamesize"] ?> spaces
- square. The first town is usually located at (0N,0E). Click the North button from the first town, and now you'll be at (1N,0E).
- Likewise, if you now click the West button, you'll be at (1N,1W). Monster levels increase with every 5 spaces you move outward
- from (0N,0E).
- While you're exploring, you will occasionally run into monsters. As in pretty much any other RPG game, you and the monster take turns
- hitting each other in an attempt to reduce each other's hit points to zero. Once you run into a monster, the Exploring screen changes
- to the Fighting screen.
- When a fight begins, you'll see the monster's name and hit points, and the game will ask you for your first command. You then get to
- pick whether you want to fight, use a spell, or run away. Note, though, that sometimes the monster has the chance to hit you
- first.
- The Fight button is pretty straightforward: you attack the monster, and the amount of damage dealt is based on your attack power and
- the monster's armor. On top of that, there are two other things that can happen: an Excellent Hit, which doubles your total attack
- damage; and a monster dodge, which results in you doing no damage to the monster.
- The Spell button allows you to pick an available spell and cast it. See the Spells list at the bottom of this page for more information
- about spells.
- Finally, there is the Run button, which lets you run away from a fight if the monster is too powerful. Be warned, though: it is
- possible for the monster to block you from running and attack you. So if your hit points are low, you may fare better by staying
- around monsters that you know can't do much damage to you.
- Once you've had your turn, the monster also gets his turn. It is also possible for you to dodge the monster's attack and take no
- damage.
- The end result of a fight is either you or the monster being knocked down to zero hit points. If you win, the monster dies and will
- give you a certain amount of experience and gold. There is also a chance that the monster will drop an item, which you can put into
- one of the three inventory slots to give you extra points in your character profile. If you lose and die, half of your gold is taken
- away - however, you are given back a few hit points to help you make it back to town (for example, if you don't have enough gold to
- pay for an Inn, and need to kill a couple low-level monsters to get the money).
- When the fight is over, you can continue exploring until you find another monster to beat into submission.
- [ Top ]
-
-
-
- Playing The Game: Status Panels
- There are two status panels on the game screen: left and right.
- The left panel inclues your current location and play status (In Town, Exploring, Fighting), compass buttons for movement, and the
- Travel To list for jumping between towns. At the bottom of the left panel is also a list of game functions.
- The right panel displays some character statistics, your inventory, and quick spells.
- The Character section shows the most important character statistics. It also displays the status bars for your current hit points,
- magic points and travel points. These status bars are colored either green, yellow or red depending on your current amount of each
- stat. There is also a link to pop up your list of extended statistics, which shows more detailed character information.
- The Fast Spells section lists any Heal spells you've learned. You may use these links any time you are in town or exploring to cast
- the heal spell. These may not be used during fights, however - you have to use the Spells box on the fight screen for that.
- [ Top ]
-
-
-
- Spoilers: Items & Drops
- Click here for the Items & Drops spoiler page.
- [ Top ]
-
-
-
- Spoilers: Monsters
- Click here for the Monsters spoiler page.
- [ Top ]
-
-
-
- Spoilers: Spells
- Click here for the Spells spoiler page.
- [ Top ]
-
-
-
- Spoilers: Levels
- Click here for the Levels spoiler page.
- [ Top ]
-
-
-
- Credits
-
- - All program code and stock graphics for the game were created by Jamin Seven.
- - Major props go to a few people on the PHP manual site, for help with various chunks of code. The specific people are listed in the source code.
- - Super monkey love goes to Enix and the developers of Dragon Warrior. If it weren't for you guys, my game never would have been made.
- - Mega props go to Dalez from GameFAQs for his DW3 experience chart, which was where I got my experience levels from.
- -
- Mad crazy ninja love goes to the following people for help and support throughout the development process:
- Ideas: (whether they got used or not)
-
- - kushet
- - lghtning
- - Ebolamonkey3000
- - Crimson Scythe
- - SilDeath
-
- Beta Testing: (forums name if applicable, character name otherwise)
-
- - Ebolamonkey3000
- - lisi
- - Junglist
- - Crimson Scythe
- - Sk8erpunk69
- - lghtning
- - kushet
- - SilDeath
- - lowrider4life
- - dubiin
- - Sam Wise The Great
-
-
-
- Apologies and lots of happy naked love to anyone I forgot.
- And of course, thanks to you for playing my game!
- NINJA!
- [ Top ]
-
-
-
- Please visit the following sites for more information:
- Se7enet (Jamin's homepage)
- Dragon Knight (official DK homepage)
- Forums (official DK forums)
- All original coding and graphics for the Dragon Knight game engine are © 2003-2005 by Jamin Seven.
- [ Top ]
-
-
-
-
-
-
- Items |
- Type | Name | Cost | Attribute | Special |
- query('SELECT * FROM items ORDER BY id;');
- $item_types = [1 => ['weapon', 'Attack'], 2 => ['armor', 'Defense'], 3 => ['shield', 'Defense']];
-
- while ($item = $items->fetchArray(SQLITE3_ASSOC)) {
- $image = $item_types[$item['type']][0];
- $power = $item_types[$item['type']][1];
-
- if ($item['special'] !== 'X') {
- $special = explode(',', $item['special']);
- $attr = special_to_string($special[0]);
- $stat = (($special[1] > 0) ? '+' : '') . $special[1];
- $bigspecial = "$attr $stat";
- } else {
- $bigspecial = 'None';
- }
-
- echo " | ".$item["name"]." | ".$item["buycost"]." Gold | ".$item["attribute"]." $power Power | $bigspecial |
\n";
- }
- ?>
-
-
-
-
-
- Drops |
- Name | Monster Level | Attribute 1 | Attribute 2 |
- query('SELECT * FROM drops ORDER BY id;');
-
- while ($drop = $drops->fetchArray(SQLITE3_ASSOC)) {
- if ($drop["attribute1"] !== "X") {
- $special = explode(',', $drop['attribute1']);
- $attr = special_to_string($special[0]);
- $stat = (($special[1] > 0) ? '+' : '') . $special[1];
- $bigspecial1 = "$attr $stat";
- } else {
- $bigspecial1 = 'None';
- }
-
- if ($drop["attribute2"] !== "X") {
- $special = explode(',', $drop['attribute2']);
- $attr = special_to_string($special[0]);
- $stat = (($special[1] > 0) ? '+' : '') . $special[1];
- $bigspecial2 = "$attr $stat";
- } else {
- $bigspecial2 = 'None';
- }
-
- echo "".$drop["name"]." | ".$drop["mlevel"]." | $bigspecial1 | $bigspecial2 |
\n";
- }
- ?>
-
-
-
-
-
-
-
- Spells |
- Name | Cost | Type | Attribute |
- query('SELECT * FROM spells ORDER BY id;');
- $spell_types = ['None', 'Heal', 'Hurt', 'Sleep', '+Damage (%)', '+Defense (%)'];
- while ($spell = $spells->fetchArray(SQLITE3_ASSOC)) {
- echo <<{$spell["name"]} |
- {$spell["mp"]} |
- {$spell_types[$spell['type']]} |
- {$spell["attribute"]} |
- HTML;
- }
- ?>
-
-
- - Heal spells always give you the maximum amount possible, until your current HP is full.
- - Hurt spells deal X damage (not always the maximum) to the monster, regardless of the monster's armor.
- - Sleep spells put the monster to sleep. The monster has an X in 15 chance of remaining asleep each turn.
- - +Damage spells increase your total attack damage by X percent until the end of the fight.
- - +Defense spells reduce the total damage you take from the monster by X percent until the end of each fight.
-
-
-
-
-
-
-
- Monsters |
- Name | Max HP | Max Damage | Armor | Level | Max Exp. | Max Gold | Immunity |
- query('SELECT * FROM monsters ORDER BY id;');
- $immunities = ['None', 'Hurt', 'Hurt & Sleep'];
-
- while ($m = $monsters->fetchArray(SQLITE3_ASSOC)) {
- $immune = $immunities[$m['immune']] ?? 'Unknown';
- echo "".$m["name"]." | ".$m["maxhp"]." | ".$m["maxdam"]." | ".$m["armor"]." | ".$m["level"]." | ".$m["maxexp"]." | ".$m["maxgold"]." | $immune |
\n";
- }
- ?>
-
-
-
-
- query('SELECT * FROM levels ORDER BY id;');
- while ($level = $levels->fetchArray(SQLITE3_ASSOC)) {
- $class_data = [1 => [], 2 => [], 3 => []];
-
- foreach ($level as $column => $value) {
- if ($column === 'id') continue;
-
- $parts = explode('_', $column);
- $class_number = (int) $parts[0];
- $attribute = $parts[1];
-
- if (in_array($class_number, [1, 2, 3])) {
- $class_data[$class_number][$level['id']][$attribute] = $value;
- }
- }
-
- $rows[$level['id']] = $class_data;
- }
-
- $spells = [];
- $spells_query = db()->query('SELECT * FROM spells ORDER BY id;');
- while ($spell = $spells_query->fetchArray(SQLITE3_ASSOC)) {
- $spells[$spell['id']] = $spell;
- }
- ?>
-
-
- echo $controlrow["class1name"]; ?> Levels |
- Level | Exp. | HP | MP | TP | Strength | Dexterity | Spell |
- $class_data) {
- $level_data = $class_data[1][$level];
-
- $spell_name = '';
- if (!empty($level_data['spells'])) $spell_name = $spells[$level_data['spells']]['name'];
-
- echo '';
- echo '' . $level . ' | ';
- echo '' . ($level_data['exp'] ?? '') . ' | ';
- echo '' . ($level_data['hp'] ?? '') . ' | ';
- echo '' . ($level_data['mp'] ?? '') . ' | ';
- echo '' . ($level_data['tp'] ?? '') . ' | ';
- echo '' . ($level_data['strength'] ?? '') . ' | ';
- echo '' . ($level_data['dexterity'] ?? '') . ' | ';
- echo '' . (!empty($spell_name) ? $spell_name : 'None') . ' | ';
- echo '
';
- }
- ?>
-
-
-
-
-
- echo $controlrow["class2name"]; ?> Levels |
- Level | Exp. | HP | MP | TP | Strength | Dexterity | Spell |
- $class_data) {
- $level_data = $class_data[2][$level];
-
- $spell_name = '';
- if (!empty($level_data['spells'])) $spell_name = $spells[$level_data['spells']]['name'];
-
- echo '';
- echo '' . $level . ' | ';
- echo '' . ($level_data['exp'] ?? '') . ' | ';
- echo '' . ($level_data['hp'] ?? '') . ' | ';
- echo '' . ($level_data['mp'] ?? '') . ' | ';
- echo '' . ($level_data['tp'] ?? '') . ' | ';
- echo '' . ($level_data['strength'] ?? '') . ' | ';
- echo '' . ($level_data['dexterity'] ?? '') . ' | ';
- echo '' . (!empty($spell_name) ? $spell_name : 'None') . ' | ';
- echo '
';
- }
- ?>
-
-
-
-
-
- echo $controlrow["class3name"]; ?> Levels |
- Level | Exp. | HP | MP | TP | Strength | Dexterity | Spell |
- $class_data) {
- $level_data = $class_data[3][$level];
-
- $spell_name = '';
- if (!empty($level_data['spells'])) $spell_name = $spells[$level_data['spells']]['name'];
-
- echo '';
- echo '' . $level . ' | ';
- echo '' . ($level_data['exp'] ?? '') . ' | ';
- echo '' . ($level_data['hp'] ?? '') . ' | ';
- echo '' . ($level_data['mp'] ?? '') . ' | ';
- echo '' . ($level_data['tp'] ?? '') . ' | ';
- echo '' . ($level_data['strength'] ?? '') . ' | ';
- echo '' . ($level_data['dexterity'] ?? '') . ' | ';
- echo '' . (!empty($spell_name) ? $spell_name : 'None') . ' | ';
- echo '
';
- }
- ?>
-
-
-
-
- Experience points listed are total values up until that point. All other values are just the new amount that you gain for each level.
-
-
-
-
-
-
-
-
diff --git a/public/index.php b/public/index.php
index 0a4fd2b..d15b626 100644
--- a/public/index.php
+++ b/public/index.php
@@ -4,37 +4,42 @@
require_once '../src/lib.php';
require_once '../src/router.php';
+require_once '../src/users.php';
+require_once '../src/help.php';
if (!file_exists('../.installed')) redirect('install.php');
$controlrow = get_control_row();
-// Login (or verify) if not logged in.
-if (($userrow = checkcookies()) === false) {
- if (isset($_GET['do']) && $_GET['do'] === 'verify') {
- header("Location: users.php?do=verify");
- exit;
- }
-
- header("Location: login.php?do=login");
- exit;
-}
-
-// Close game.
-if ((bool) $controlrow["gameopen"] === false) {
+if (!$controlrow["gameopen"]) {
display("The game is currently closed for maintanence. Please check back later.", "Game Closed");
exit;
}
-// Force verify if the user isn't verified yet.
-if ((bool) $controlrow["verifyemail"] && (bool) $userrow["verify"] === false) {
- header("Location: users.php?do=verify");
- exit;
+// Login (or verify) if not logged in.
+if (($userrow = checkcookies()) === false) {
+ $uri = uri();
+ if (!in_array($uri[0], ['login', 'register', 'verify', 'lostpassword', 'help'])) {
+ redirect('/login');
+ }
+} else {
+ // Block user if he/she has been banned.
+ if ($userrow["authlevel"] === 2) {
+ exit("Your account has been banned.");
+ }
+
+ // Force verify if the user isn't verified yet.
+ if ($controlrow["verifyemail"] && (bool) $userrow["verify"] === false) {
+ redirect('/verify');
+ header("Location: users.php?do=verify");
+ exit;
+ }
}
-// Block user if he/she has been banned.
-if ($userrow["authlevel"] === 2) {
- exit("Your account has been blocked.");
+// Close game.
+if (!$controlrow["gameopen"]) {
+ display("The game is currently closed for maintanence. Please check back later.", "Game Closed");
+ exit;
}
require_once '../src/towns.php';
@@ -58,35 +63,19 @@ $r->get('/', function() {
}
display($page, $title);
-});
+})->middleware('auth_only');
$r->get('/ninja', function() {
exit('NINJA! 🥷');
});
-$r->get('/inn', 'inn');
-$r->post('/inn', 'inn');
-$r->get('/buy', 'buy');
-$r->get('/buy2/:id', 'buy2');
-$r->post('/buy3/:id', 'buy3');
-// $r->get('/sell', 'sell');
-$r->get('/maps', 'maps');
-$r->get('/maps2/:id', 'maps2');
-$r->post('/maps3/:id', 'maps3');
-$r->get('/gotown/:id', 'travelto');
+Towns\register_routes($r);
+Fights\register_routes($r);
+Users\register_routes($r);
+Help\register_routes($r);
$r->post('/move', 'move');
-$r->get('/fight', 'fight');
-$r->post('/fight', 'fight');
-$r->get('/victory', 'victory');
-$r->get('/drop', 'drop');
-$r->post('/drop', 'drop');
-$r->get('/dead', 'dead');
-
-$r->get('/verify', function() {
- redirect('users.php?do=verify');
-});
$r->get('/spell/:id', 'healspells');
$r->get('/showchar', 'showchar');
$r->get('/onlinechar/:id', 'onlinechar');
diff --git a/public/login.php b/public/login.php
deleted file mode 100644
index 8c90dfa..0000000
--- a/public/login.php
+++ /dev/null
@@ -1,55 +0,0 @@
- login(),
- 'logout' => logout()
-};
-
-function login()
-{
- if (checkcookies() !== false) redirect('/');
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $form = validate($_POST, [
- 'username' => ['length:3-18', 'alpha-spaces'],
- 'password' => ['length:6-255'],
- 'remember' => ['bool']
- ]);
-
- if (!$form['valid']) {
- exit(ul_from_validate_errors($form['errors']));
- }
-
- $form = $form['data'];
-
- $query = db()->query('SELECT id, username, password FROM users WHERE username = ? COLLATE NOCASE LIMIT 1;', [$form['username']]);
- $row = $query ? $query->fetchArray(SQLITE3_ASSOC) : false;
-
- if ($row === false || !password_verify($_POST['password'] ?? '', $row['password']))
- die("Invalid username or password. Please go back and try again.");
-
- $expiretime = $form['remember'] ? time() + 31536000 : 0;
- $rememberme = $form['remember'] ? 1 : 0;
- $cookie = implode(' ', [$row['id'], $row['username'], $row['password'], $rememberme]);
-
- set_cookie("dkgame", $cookie, $expiretime);
- redirect('/');
- }
-
- $page = gettemplate("login");
- $title = "Log In";
- display($page, $title, false, false, false, false);
-}
-
-
-function logout()
-{
- set_cookie("dkgame", "", -3600);
- redirect('login.php?do=login');
-}
diff --git a/src/fight.php b/src/fight.php
index 3114a70..0c58e1c 100644
--- a/src/fight.php
+++ b/src/fight.php
@@ -2,6 +2,21 @@
// fight.php :: Handles all fighting action.
+namespace Fights;
+
+use Router;
+
+function register_routes(Router $r): Router
+{
+ $r->get('/fight', 'Fights\fight');
+ $r->post('/fight', 'Fights\fight');
+ $r->get('/victory', 'Fights\victory');
+ $r->get('/drop', 'Fights\drop');
+ $r->post('/drop', 'Fights\drop');
+ $r->get('/dead', 'Fights\dead');
+ return $r;
+}
+
/**
* One big long function that determines the outcome of the fight.
*/
diff --git a/src/help.php b/src/help.php
new file mode 100644
index 0000000..741c9e7
--- /dev/null
+++ b/src/help.php
@@ -0,0 +1,500 @@
+get('/help', 'Help\main');
+ $r->get('/help/items', 'Help\items');
+ $r->get('/help/spells', 'Help\spells');
+ $r->get('/help/monsters', 'Help\monsters');
+ $r->get('/help/levels', 'Help\levels');
+ return $r;
+}
+
+function main()
+{
+ global $controlrow;
+
+ $page = <<Table of Contents
+
+
+
+
+ Introduction
+ Firstly, I'd like to say thank you for playing my game. The Dragon Knight game engine is the result of several months of
+ planning, coding and testing. The original idea was to create a web-based tribute to the NES game, Dragon
+ Warrior. In its current iteration, only the underlying fighting system really resembles that game, as almost
+ everything else in DK has been made bigger and better. But you should still recognize bits and pieces as stemming
+ from Dragon Warrior and other RPGs of old.
+ This is the first game I've ever written, and it has definitely been a positive experience. It got difficult at
+ times, admittedly, but it was still a lot of fun to write, and even more fun to play. And I hope to use this
+ experience so that if I ever want to create another game it will be even better than this one.
+ If you are a site administrator, and would like to install a copy of DK on your own server, you may visit the
+ development site for Dragon Knight. This page
+ includes the downloadable game souce code, as well as some other resources that developers and administrators may
+ find valuable.
+ Once again, thanks for playing!
+ Jamin Seven
+ Dragon Knight creator
+ My Homepage
+ Dragon Knight Homepage
+ [ Top ]
+
+
+
+ Character Classes
+ There are three character classes in the game. The main differences between the classes are what spells you get
+ access to, the speed with which you level up, and the amount of HP/MP/strength/dexterity you gain per level. Below
+ is a basic outline of each of the character classes. For more detailed information about the characters, please
+ view the Levels table at the bottom of this page. Also, note that the outline below refers to the stock class setup
+ for the game. If your administrator has used his/her own class setup, this information may not be accurate.
+ {{class1name}}
+
+ - Fast level-ups
+ - High hit points
+ - High magic points
+ - Low strength
+ - Low dexterity
+ - 5 heal spells
+ - 5 hurt spells
+ - 3 sleep spells
+ - 3 +defense spells
+ - 0 +attack spells
+
+ {{class2name}}
+
+ - Medium level-ups
+ - Medium hit points
+ - Low magic points
+ - High strength
+ - Low dexterity
+ - 3 heal spells
+ - 3 hurt spells
+ - 2 sleep spells
+ - 3 +defense spells
+ - 3 +attack spells
+
+ {{class3name}}
+
+ - Slow level-ups
+ - Medium hit points
+ - Medium magic points
+ - Low strength
+ - High dexterity
+ - 4 heal spells
+ - 4 hurt spells
+ - 3 sleep spells
+ - 2 +defense spells
+ - 2 +attack spells
+
+ [ Top ]
+
+
+
+ Playing The Game: In Town
+ When you begin a new game, the first thing you see is the Town screen. Towns serve four primary functions: healing, buying items,
+ buying maps, and displaying game information.
+ To heal yourself, click the "Rest at the Inn" link at the top of the town screen. Each town's Inn has a different price - some towns
+ are cheap, others are expensive. No matter what town you're in, the Inns always serve the same function: they restore your current
+ hit points, magic points, and travel points to their maximum amounts. Out in the field, you are free to use healing spells to restore
+ your hit points, but when you run low on magic points, the only way to restore them is at an Inn.
+ Buying weapons and armor is accomplished through the appropriately-named "Buy Weapons/Armor" link. Not every item is available in
+ every town, so in order to get the most powerful items, you'll need to explore some of the outer towns. Once you've clicked the link,
+ you are presented with a list of items available in this town's store. To the left of each item is an icon that represents its type:
+ weapon, armor or shield. The amount of attack/defense power, as well as the item's price, are displayed to the right of the item name.
+ You'll notice that some items have a red asterisk (*) next to their names. These are items that come
+ with special attributes that modify other parts of your character profile. See the Items & Drops table at the bottom of this page for
+ more information about special items.
+ Maps are the third function in towns. Buying a map to a town places the town in your Travel To box in the left status panel. Once
+ you've purchased a town's map, you can click its name from your Travel To box and you will jump to that town. Travelling this way
+ costs travel points, though, and you'll only be able to visit towns if you have enough travel points.
+ The final function in towns is displaying game information and statistics. This includes the latest news post made by the game
+ administrator, a list of players who have been online recently, and the Babble Box.
+ [ Top ]
+
+
+
+ Playing The Game: Exploring & Fighting
+ Once you're done in town, you are free to start exploring the world. Use the compass buttons on the left status panel to move around.
+ The game world is basically a big square, divided into four quadrants. Each quadrant is {{gamesize}} spaces
+ square. The first town is usually located at (0N,0E). Click the North button from the first town, and now you'll be at (1N,0E).
+ Likewise, if you now click the West button, you'll be at (1N,1W). Monster levels increase with every 5 spaces you move outward
+ from (0N,0E).
+ While you're exploring, you will occasionally run into monsters. As in pretty much any other RPG game, you and the monster take turns
+ hitting each other in an attempt to reduce each other's hit points to zero. Once you run into a monster, the Exploring screen changes
+ to the Fighting screen.
+ When a fight begins, you'll see the monster's name and hit points, and the game will ask you for your first command. You then get to
+ pick whether you want to fight, use a spell, or run away. Note, though, that sometimes the monster has the chance to hit you
+ first.
+ The Fight button is pretty straightforward: you attack the monster, and the amount of damage dealt is based on your attack power and
+ the monster's armor. On top of that, there are two other things that can happen: an Excellent Hit, which doubles your total attack
+ damage; and a monster dodge, which results in you doing no damage to the monster.
+ The Spell button allows you to pick an available spell and cast it. See the Spells list at the bottom of this page for more information
+ about spells.
+ Finally, there is the Run button, which lets you run away from a fight if the monster is too powerful. Be warned, though: it is
+ possible for the monster to block you from running and attack you. So if your hit points are low, you may fare better by staying
+ around monsters that you know can't do much damage to you.
+ Once you've had your turn, the monster also gets his turn. It is also possible for you to dodge the monster's attack and take no
+ damage.
+ The end result of a fight is either you or the monster being knocked down to zero hit points. If you win, the monster dies and will
+ give you a certain amount of experience and gold. There is also a chance that the monster will drop an item, which you can put into
+ one of the three inventory slots to give you extra points in your character profile. If you lose and die, half of your gold is taken
+ away - however, you are given back a few hit points to help you make it back to town (for example, if you don't have enough gold to
+ pay for an Inn, and need to kill a couple low-level monsters to get the money).
+ When the fight is over, you can continue exploring until you find another monster to beat into submission.
+ [ Top ]
+
+
+
+ Playing The Game: Status Panels
+ There are two status panels on the game screen: left and right.
+ The left panel inclues your current location and play status (In Town, Exploring, Fighting), compass buttons for movement, and the
+ Travel To list for jumping between towns. At the bottom of the left panel is also a list of game functions.
+ The right panel displays some character statistics, your inventory, and quick spells.
+ The Character section shows the most important character statistics. It also displays the status bars for your current hit points,
+ magic points and travel points. These status bars are colored either green, yellow or red depending on your current amount of each
+ stat. There is also a link to pop up your list of extended statistics, which shows more detailed character information.
+ The Fast Spells section lists any Heal spells you've learned. You may use these links any time you are in town or exploring to cast
+ the heal spell. These may not be used during fights, however - you have to use the Spells box on the fight screen for that.
+ [ Top ]
+
+
+
+ Spoilers: Items & Drops
+ Click here for the Items & Drops spoiler page.
+ [ Top ]
+
+
+
+ Spoilers: Monsters
+ Click here for the Monsters spoiler page.
+ [ Top ]
+
+
+
+ Spoilers: Spells
+ Click here for the Spells spoiler page.
+ [ Top ]
+
+
+
+ Spoilers: Levels
+ Click here for the Levels spoiler page.
+ [ Top ]
+
+
+
+ Credits
+
+ - All program code and stock graphics for the game were created by Jamin Seven.
+ - Major props go to a few people on the PHP manual site, for help with various chunks of code. The specific people are listed in the source code.
+ - Super monkey love goes to Enix and the developers of Dragon Warrior. If it weren't for you guys, my game never would have been made.
+ - Mega props go to Dalez from GameFAQs for his DW3 experience chart, which was where I got my experience levels from.
+ -
+ Mad crazy ninja love goes to the following people for help and support throughout the development process:
+ Ideas: (whether they got used or not)
+
+ - kushet
+ - lghtning
+ - Ebolamonkey3000
+ - Crimson Scythe
+ - SilDeath
+
+ Beta Testing: (forums name if applicable, character name otherwise)
+
+ - Ebolamonkey3000
+ - lisi
+ - Junglist
+ - Crimson Scythe
+ - Sk8erpunk69
+ - lghtning
+ - kushet
+ - SilDeath
+ - lowrider4life
+ - dubiin
+ - Sam Wise The Great
+
+
+
+ Apologies and lots of happy naked love to anyone I forgot.
+ And of course, thanks to you for playing my game!
+ NINJA!
+ [ Top ]
+
+
+
+ Please visit the following sites for more information:
+ Se7enet (Jamin's homepage)
+ Dragon Knight (official DK homepage)
+ Forums (official DK forums)
+ All original coding and graphics for the Dragon Knight game engine are © 2003-2005 by Jamin Seven.
+ [ Top ]
+ HTML;
+
+ display_help(parsetemplate($page, $controlrow));
+}
+
+function items()
+{
+ global $controlrow;
+
+ $page = <<
+ Items |
+ Type | Name | Cost | Attribute | Special |
+ HTML;
+
+ $items = db()->query('SELECT * FROM items ORDER BY id;');
+ $item_types = [1 => ['weapon', 'Attack'], 2 => ['armor', 'Defense'], 3 => ['shield', 'Defense']];
+
+ while ($item = $items->fetchArray(SQLITE3_ASSOC)) {
+ $image = $item_types[$item['type']][0];
+ $power = $item_types[$item['type']][1];
+
+ if ($item['special'] !== 'X') {
+ $special = explode(',', $item['special']);
+ $attr = special_to_string($special[0]);
+ $stat = (($special[1] > 0) ? '+' : '') . $special[1];
+ $bigspecial = "$attr $stat";
+ } else {
+ $bigspecial = 'None';
+ }
+
+ $page .= " | ".$item["name"]." | ".$item["buycost"]." Gold | ".$item["attribute"]." $power Power | $bigspecial |
\n";
+ }
+
+ $page .= <<
+
+
+
+
+ Drops |
+ Name | Monster Level | Attribute 1 | Attribute 2 |
+ HTML;
+
+ $drops = db()->query('SELECT * FROM drops ORDER BY id;');
+
+ while ($drop = $drops->fetchArray(SQLITE3_ASSOC)) {
+ if ($drop["attribute1"] !== "X") {
+ $special = explode(',', $drop['attribute1']);
+ $attr = special_to_string($special[0]);
+ $stat = (($special[1] > 0) ? '+' : '') . $special[1];
+ $bigspecial1 = "$attr $stat";
+ } else {
+ $bigspecial1 = 'None';
+ }
+
+ if ($drop["attribute2"] !== "X") {
+ $special = explode(',', $drop['attribute2']);
+ $attr = special_to_string($special[0]);
+ $stat = (($special[1] > 0) ? '+' : '') . $special[1];
+ $bigspecial2 = "$attr $stat";
+ } else {
+ $bigspecial2 = 'None';
+ }
+
+ $page .= "".$drop["name"]." | ".$drop["mlevel"]." | $bigspecial1 | $bigspecial2 |
\n";
+ }
+ $page .= '
';
+
+ display_help($page);
+}
+
+
+function spells()
+{
+ global $controlrow;
+
+ $page = <<
+ Spells |
+ Name | Cost | Type | Attribute |
+ HTML;
+
+ $spells = db()->query('SELECT * FROM spells ORDER BY id;');
+ $spell_types = ['None', 'Heal', 'Hurt', 'Sleep', '+Damage (%)', '+Defense (%)'];
+ while ($spell = $spells->fetchArray(SQLITE3_ASSOC)) {
+ $page .= <<{$spell["name"]} |
+ {$spell["mp"]} |
+ {$spell_types[$spell['type']]} |
+ {$spell["attribute"]} |
+ HTML;
+ }
+
+ $page .= <<
+
+ - Heal spells always give you the maximum amount possible, until your current HP is full.
+ - Hurt spells deal X damage (not always the maximum) to the monster, regardless of the monster's armor.
+ - Sleep spells put the monster to sleep. The monster has an X in 15 chance of remaining asleep each turn.
+ - +Damage spells increase your total attack damage by X percent until the end of the fight.
+ - +Defense spells reduce the total damage you take from the monster by X percent until the end of each fight.
+
+ HTML;
+
+ display_help($page);
+}
+
+function monsters()
+{
+ $page = <<
+ Monsters |
+ Name | Max HP | Max Damage | Armor | Level | Max Exp. | Max Gold | Immunity |
+ HTML;
+
+ $monsters = db()->query('SELECT * FROM monsters ORDER BY id;');
+ $immunities = ['None', 'Hurt', 'Hurt & Sleep'];
+
+ while ($m = $monsters->fetchArray(SQLITE3_ASSOC)) {
+ $immune = $immunities[$m['immune']] ?? 'Unknown';
+ $page .= "".$m["name"]." | ".$m["maxhp"]." | ".$m["maxdam"]." | ".$m["armor"]." | ".$m["level"]." | ".$m["maxexp"]." | ".$m["maxgold"]." | $immune |
\n";
+ }
+
+ display_help($page.'');
+}
+
+function levels()
+{
+ global $controlrow;
+
+ $rows = [];
+
+ $levels = db()->query('SELECT * FROM levels ORDER BY id;');
+ while ($level = $levels->fetchArray(SQLITE3_ASSOC)) {
+ $class_data = [1 => [], 2 => [], 3 => []];
+
+ foreach ($level as $column => $value) {
+ if ($column === 'id') continue;
+
+ $parts = explode('_', $column);
+ $class_number = (int) $parts[0];
+ $attribute = $parts[1];
+
+ if (in_array($class_number, [1, 2, 3])) {
+ $class_data[$class_number][$level['id']][$attribute] = $value;
+ }
+ }
+
+ $rows[$level['id']] = $class_data;
+ }
+
+ $spells = [];
+ $spells_query = db()->query('SELECT * FROM spells ORDER BY id;');
+ while ($spell = $spells_query->fetchArray(SQLITE3_ASSOC)) {
+ $spells[$spell['id']] = $spell;
+ }
+
+ $page = <<
+ {{class1name}} Levels |
+ Level | Exp. | HP | MP | TP | Strength | Dexterity | Spell |
+ HTML;
+ foreach ($rows as $level => $class_data) {
+ $level_data = $class_data[1][$level];
+
+ $spell_name = '';
+ if (!empty($level_data['spells'])) $spell_name = $spells[$level_data['spells']]['name'];
+
+ $page .= '';
+ $page .= '' . $level . ' | ';
+ $page .= '' . ($level_data['exp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['hp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['mp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['tp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['strength'] ?? '') . ' | ';
+ $page .= '' . ($level_data['dexterity'] ?? '') . ' | ';
+ $page .= '' . (!empty($spell_name) ? $spell_name : 'None') . ' | ';
+ $page .= '
';
+ }
+
+ $page .= <<
+
+
+
+
+ {{class2name}} Levels |
+ Level | Exp. | HP | MP | TP | Strength | Dexterity | Spell |
+ HTML;
+
+ foreach ($rows as $level => $class_data) {
+ $level_data = $class_data[2][$level];
+
+ $spell_name = '';
+ if (!empty($level_data['spells'])) $spell_name = $spells[$level_data['spells']]['name'];
+
+ $page .= '';
+ $page .= '' . $level . ' | ';
+ $page .= '' . ($level_data['exp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['hp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['mp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['tp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['strength'] ?? '') . ' | ';
+ $page .= '' . ($level_data['dexterity'] ?? '') . ' | ';
+ $page .= '' . (!empty($spell_name) ? $spell_name : 'None') . ' | ';
+ $page .= '
';
+ }
+
+ $page .= <<
+
+
+
+
+ {{class3name}} Levels |
+ Level | Exp. | HP | MP | TP | Strength | Dexterity | Spell |
+ HTML;
+
+ foreach ($rows as $level => $class_data) {
+ $level_data = $class_data[3][$level];
+
+ $spell_name = '';
+ if (!empty($level_data['spells'])) $spell_name = $spells[$level_data['spells']]['name'];
+
+ $page .= '';
+ $page .= '' . $level . ' | ';
+ $page .= '' . ($level_data['exp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['hp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['mp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['tp'] ?? '') . ' | ';
+ $page .= '' . ($level_data['strength'] ?? '') . ' | ';
+ $page .= '' . ($level_data['dexterity'] ?? '') . ' | ';
+ $page .= '' . (!empty($spell_name) ? $spell_name : 'None') . ' | ';
+ $page .= '
';
+ }
+
+ $page .= <<
+
+
+
+ Experience points listed are total values up until that point. All other values are just the new amount that you gain for each level.
+ HTML;
+
+ display_help(parsetemplate($page, $controlrow));
+}
+
+function display_help(string $content)
+{
+ global $controlrow;
+ $template = parsetemplate(gettemplate('help'), $controlrow);
+ echo parsetemplate($template, ['content' => $content]);
+}
diff --git a/src/lib.php b/src/lib.php
index 85b4b3a..c1af9f3 100644
--- a/src/lib.php
+++ b/src/lib.php
@@ -96,7 +96,7 @@ function admindisplay($content, $title)
function display($content, $title, $topnav=true, $leftnav=true, $rightnav=true, $badstart=false) { // Finalize page and output to browser.
- global $numqueries, $userrow, $controlrow, $version, $build;
+ global $userrow, $controlrow;
if (!isset($controlrow)) {
$query = db()->query('SELECT * FROM control WHERE id=1 LIMIT 1;');
$controlrow = $query->fetchArray(SQLITE3_ASSOC);
@@ -106,12 +106,19 @@ function display($content, $title, $topnav=true, $leftnav=true, $rightnav=true,
if ($rightnav == true) { $rightnav = gettemplate("rightnav"); } else { $rightnav = ""; }
if ($leftnav == true) { $leftnav = gettemplate("leftnav"); } else { $leftnav = ""; }
if ($topnav == true) {
- $topnav = " ";
+ $topnav = <<
+
+ HTML;
} else {
- $topnav = " ";
+ $topnav = <<
+
+
+ HTML;
}
- if (isset($userrow)) {
+ if (isset($userrow) && $userrow !== false) {
// Get userrow again, in case something has been updated.
$userquery = db()->query('SELECT * FROM users WHERE id = ? LIMIT 1;', [$userrow['id']]);
@@ -526,3 +533,27 @@ function ul_from_validate_errors(array $errors): string
}
return $string . '';
}
+
+/**
+ * Get the URI, broken up into chunks.
+ */
+function uri(): array
+{
+ return explode('/', trim($_SERVER['REQUEST_URI'], '/'));
+}
+
+/**
+ * Redirect to login if not authenticated.
+ */
+function auth_only(): void
+{
+ if (!checkcookies()) redirect('/login');
+}
+
+/**
+ * Redirect to home if authenticated.
+ */
+function guest_only(): void
+{
+ if (checkcookies()) redirect('/login');
+}
diff --git a/src/towns.php b/src/towns.php
index 9b24345..6383507 100644
--- a/src/towns.php
+++ b/src/towns.php
@@ -2,6 +2,25 @@
// towns.php :: Handles all actions you can do in town.
+namespace Towns;
+
+use Router;
+
+function register_routes(Router $r): Router
+{
+ $r->get('/inn', 'Towns\inn');
+ $r->post('/inn', 'Towns\inn');
+ $r->get('/buy', 'Towns\buy');
+ $r->get('/buy2/:id', 'Towns\buy2');
+ $r->post('/buy3/:id', 'Towns\buy3');
+ // $r->get('/sell', 'Towns\sell');
+ $r->get('/maps', 'Towns\maps');
+ $r->get('/maps2/:id', 'Towns\maps2');
+ $r->post('/maps3/:id', 'Towns\maps3');
+ $r->get('/gotown/:id', 'Towns\travelto');
+ return $r;
+}
+
/**
* Staying at the inn resets all expendable stats to their max values.
*/
diff --git a/public/users.php b/src/users.php
similarity index 68%
rename from public/users.php
rename to src/users.php
index 29d8120..d90c731 100644
--- a/public/users.php
+++ b/src/users.php
@@ -1,20 +1,72 @@
get('/login', 'Users\login');
+ $r->post('/login', 'Users\login');
+ $r->get('/logout', 'Users\logout');
+ $r->get('/register', 'Users\register');
+ $r->post('/register', 'Users\register');
+ $r->get('/lostpassword', 'Users\lostpassword');
+ $r->post('/lostpassword', 'Users\lostpassword');
+ $r->get('/changepassword', 'Users\changepassword');
+ $r->post('/changepassword', 'Users\changepassword');
+ $r->get('/verify', 'Users\verify');
+ $r->post('/verify', 'Users\verify');
+ return $r;
+}
-$controlrow = get_control_row();
+/**
+ * Displays the login page, and processes login requests.
+ */
+function login()
+{
+ if (checkcookies() !== false) redirect('/');
-$do = $_GET['do'] ?? 'register';
-match ($do) {
- 'verify' => verify(),
- 'lostpassword' => lostpassword(),
- 'changepassword' => changepassword(),
- default => register()
-};
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $form = validate($_POST, [
+ 'username' => ['length:3-18', 'alpha-spaces'],
+ 'password' => ['length:6-255'],
+ 'remember' => ['bool']
+ ]);
+
+ if (!$form['valid']) {
+ exit(ul_from_validate_errors($form['errors']));
+ }
+
+ $form = $form['data'];
+
+ $query = db()->query('SELECT id, username, password FROM users WHERE username = ? COLLATE NOCASE LIMIT 1;', [$form['username']]);
+ $row = $query ? $query->fetchArray(SQLITE3_ASSOC) : false;
+
+ if ($row === false || !password_verify($_POST['password'] ?? '', $row['password']))
+ die("Invalid username or password. Please go back and try again.");
+
+ $expiretime = $form['remember'] ? time() + 31536000 : 0;
+ $rememberme = $form['remember'] ? 1 : 0;
+ $cookie = implode(' ', [$row['id'], $row['username'], $row['password'], $rememberme]);
+
+ set_cookie("dkgame", $cookie, $expiretime);
+ redirect('/');
+ }
+
+ $page = gettemplate("login");
+ $title = "Log In";
+ display($page, $title, false, false, false, false);
+}
+
+/**
+ * Delete the current cookie and redirect to home.
+ */
+function logout()
+{
+ set_cookie("dkgame", "", -3600);
+ redirect('/login');
+}
/**
* Register a new account.
@@ -51,7 +103,7 @@ function register()
$page = "Your account was created successfully.
However, there was a problem sending your verification email. Please check with the game administrator to help resolve this problem.";
}
} else {
- $page = "Your account was created succesfully.
You may now continue to the Login Page and continue playing ".$controlrow["gamename"]."!";
+ $page = "Your account was created succesfully.
You may now continue to the Login Page and continue playing ".$controlrow["gamename"]."!";
}
}
} else {
@@ -79,10 +131,10 @@ function verify()
db()->query("UPDATE users SET verify='g2g' WHERE username=?;", [$u]);
- display("Your account was verified successfully.
You may now continue to the Login Page and start playing the game.
Thanks for playing!","Verify Email",false,false,false);
+ display("Your account was verified successfully.
You may now continue to the Login Page and start playing the game.
Thanks for playing!","Verify Email",false,false,false);
}
- $topnav = "";
+ $topnav = "";
display(gettemplate("verify"), "Verify Email", false, false, false);
}
@@ -99,13 +151,13 @@ function lostpassword()
db()->query('UPDATE users SET password=? WHERE email=?;', [$hashed, $e]);
if (sendpassemail($e, $newpass)) {
- display("Your new password was emailed to the address you provided.
Once you receive it, you may Log In and continue playing.
Thank you.","Lost Password",false,false,false);
+ display("Your new password was emailed to the address you provided.
Once you receive it, you may Log In and continue playing.
Thank you.","Lost Password",false,false,false);
} else {
display("There was an error sending your new password.
Please check with the game administrator for more information.
We apologize for the inconvience.","Lost Password",false,false,false);
}
}
- $topnav = "";
+ $topnav = "";
display(gettemplate("lostpassword"), "Lost Password", false, false, false);
}
@@ -137,10 +189,10 @@ function changepassword()
set_cookie('dkgame', '', -3600);
- display("Your password was changed successfully.
You have been logged out of the game to avoid errors.
Please log back in to continue playing.","Change Password",false,false,false);
+ display("Your password was changed successfully.
You have been logged out of the game to avoid errors.
Please log back in to continue playing.","Change Password",false,false,false);
}
- $topnav = "";
+ $topnav = "";
display(gettemplate("changepassword"), "Change Password", false, false, false);
}
diff --git a/templates/help.php b/templates/help.php
new file mode 100644
index 0000000..8d69293
--- /dev/null
+++ b/templates/help.php
@@ -0,0 +1,85 @@
+
+
+
+ {{gamename}} Help
+
+
+
+
+ {{gamename}} Help
+ [ Return to the game ]
+
+
+
+ {{content}}
+
+
+
+
+
+
+HTML;
diff --git a/templates/leftnav.php b/templates/leftnav.php
index 4e1e540..1b50df3 100644
--- a/templates/leftnav.php
+++ b/templates/leftnav.php
@@ -29,7 +29,7 @@ $template = <<Change Password
- Log Out
- Help
+ Log Out
+ Help
HTML;
diff --git a/templates/login.php b/templates/login.php
index dbd5146..3fd02ea 100644
--- a/templates/login.php
+++ b/templates/login.php
@@ -1,7 +1,7 @@
+