diff --git a/assets/dk.css b/assets/dk.css
index 91fc988..56391bb 100644
--- a/assets/dk.css
+++ b/assets/dk.css
@@ -576,4 +576,8 @@ div.bottom-reply {
.mt-1 {
margin-top: 1rem;
+}
+
+hr {
+ margin: 1rem 0;
}
\ No newline at end of file
diff --git a/data/dk.db b/data/dk.db
index 727455e..fda1a74 100644
Binary files a/data/dk.db and b/data/dk.db differ
diff --git a/internal/routes/forum.go b/internal/routes/forum.go
index 0ab1d86..946374a 100644
--- a/internal/routes/forum.go
+++ b/internal/routes/forum.go
@@ -3,6 +3,7 @@ package routes
import (
"dk/internal/components"
"dk/internal/database"
+ "dk/internal/helpers"
"dk/internal/helpers/markdown"
"dk/internal/models/forum"
"dk/internal/models/users"
@@ -15,8 +16,7 @@ import (
"git.sharkk.net/Sharkk/Sushi/auth"
)
-// ThreadData flattened struct for template use
-type ThreadData struct {
+type threadData struct {
ID int
Posted int64
LastPost int64
@@ -34,8 +34,7 @@ type ThreadData struct {
HasReplies bool
}
-// ReplyData flattened struct for template use
-type ReplyData struct {
+type replyData struct {
ID int
Posted int64
Author int
@@ -46,47 +45,18 @@ type ReplyData struct {
AuthorClass string
}
-// Helper methods for ThreadData
-func (t *ThreadData) PostedTime() time.Time {
+func (t *threadData) PostedTime() time.Time {
return time.Unix(t.Posted, 0)
}
-func (t *ThreadData) LastPostTime() time.Time {
+func (t *threadData) LastPostTime() time.Time {
return time.Unix(t.LastPost, 0)
}
-// Helper methods for ReplyData
-func (r *ReplyData) PostedTime() time.Time {
+func (r *replyData) PostedTime() time.Time {
return time.Unix(r.Posted, 0)
}
-// PaginationParams handles common pagination logic
-type PaginationParams struct {
- Page int
- PerPage int
- Total int
-}
-
-func (p PaginationParams) Offset() int {
- return (p.Page - 1) * p.PerPage
-}
-
-func (p PaginationParams) TotalPages() int {
- pages := (p.Total + p.PerPage - 1) / p.PerPage
- if pages < 1 {
- return 1
- }
- return pages
-}
-
-func (p PaginationParams) HasNext() bool {
- return p.Page < p.TotalPages()
-}
-
-func (p PaginationParams) HasPrev() bool {
- return p.Page > 1
-}
-
func RegisterForumRoutes(app *sushi.App) {
authed := app.Group("/forum")
authed.Use(auth.RequireAuth())
@@ -117,19 +87,19 @@ func validateThread(ctx sushi.Ctx, id int) (*forum.Forum, bool) {
}
// Helper function to get pagination params from request
-func getPaginationParams(ctx sushi.Ctx, perPage int) PaginationParams {
+func getPagination(ctx sushi.Ctx, perPage int) helpers.Pagination {
page := max(int(ctx.QueryArgs().GetUintOrZero("page")), 1)
- return PaginationParams{
+ return helpers.Pagination{
Page: page,
PerPage: perPage,
}
}
func index(ctx sushi.Ctx) {
- pagination := getPaginationParams(ctx, 30)
+ pagination := getPagination(ctx, 30)
// Get threads with user data
- var threads []*ThreadData
+ var threads []*threadData
query := `
SELECT f.id, f.posted, f.last_post, f.author, f.parent, f.replies, f.title, f.content,
COALESCE(u.username, '[Deleted]') as author_username,
@@ -145,7 +115,7 @@ func index(ctx sushi.Ctx) {
err := database.Select(&threads, query, pagination.PerPage, pagination.Offset())
if err != nil {
log.Printf("Error fetching forum threads: %v", err)
- threads = make([]*ThreadData, 0)
+ threads = make([]*threadData, 0)
}
// Get last reply info for each thread
@@ -235,11 +205,11 @@ func showThread(ctx sushi.Ctx) {
return
}
- pagination := getPaginationParams(ctx, 10)
+ pagination := getPagination(ctx, 10)
pagination.Total = thread.Replies
// Get thread with author data
- var threadData ThreadData
+ var threadData threadData
err := database.Get(&threadData, `
SELECT f.id, f.posted, f.last_post, f.author, f.parent, f.replies, f.title, f.content,
COALESCE(u.username, '[Deleted]') as author_username,
@@ -259,7 +229,7 @@ func showThread(ctx sushi.Ctx) {
}
// Get replies with user data
- var replies []*ReplyData
+ var replies []*replyData
err = database.Select(&replies, `
SELECT f.id, f.posted, f.author, f.title, f.content,
COALESCE(u.username, '[Deleted]') as author_username,
@@ -274,7 +244,7 @@ func showThread(ctx sushi.Ctx) {
if err != nil {
log.Printf("Error loading replies for thread %d: %v", id, err)
- replies = make([]*ReplyData, 0)
+ replies = make([]*replyData, 0)
}
components.RenderPage(ctx, threadData.Title, "forum/thread.html", map[string]any{
diff --git a/internal/routes/help.go b/internal/routes/help.go
new file mode 100644
index 0000000..65db1b8
--- /dev/null
+++ b/internal/routes/help.go
@@ -0,0 +1,16 @@
+package routes
+
+import (
+ "dk/internal/components"
+
+ sushi "git.sharkk.net/Sharkk/Sushi"
+)
+
+func RegisterHelpRoutes(app *sushi.App) {
+ group := app.Group("/help")
+ group.Get("/", guide)
+}
+
+func guide(ctx sushi.Ctx) {
+ components.RenderPage(ctx, "Help", "help/guide.html", map[string]any{})
+}
diff --git a/main.go b/main.go
index 2e8785f..9cdc5ca 100644
--- a/main.go
+++ b/main.go
@@ -180,6 +180,7 @@ func start(port string) error {
routes.RegisterTownRoutes(app)
routes.RegisterFightRoutes(app)
routes.RegisterForumRoutes(app)
+ routes.RegisterHelpRoutes(app)
app.Get("/assets/*path", sushi.Static(cwd))
diff --git a/templates/help/guide.html b/templates/help/guide.html
new file mode 100644
index 0000000..676a122
--- /dev/null
+++ b/templates/help/guide.html
@@ -0,0 +1,190 @@
+{include "layout.html"}
+
+{block "content"}
+
+
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.
+@TODO
+
+
+
+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 ]
+
+
+
+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 {control.WorldSize} 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 ]
+
+
+
+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 ]
+
+
+
+Items & Drops
+Click here for the Items & Drops spoiler page.
+[ Top ]
+
+
+
+Monsters
+Click here for the Monsters spoiler page.
+[ Top ]
+
+
+
+Spells
+Click here for the Spells 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 ]
+{/block}