clean up helpers package, remove orderedmaps and clean up list generation
This commit is contained in:
parent
fc30d04ccb
commit
41eed92929
11
data/control.json
Normal file
11
data/control.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"world_size": 200,
|
||||
"open": 1,
|
||||
"admin_email": "",
|
||||
"email_mode": "file",
|
||||
"email_file_path": "data/emails.txt",
|
||||
"smtp_host": "",
|
||||
"smtp_port": "587",
|
||||
"smtp_username": "",
|
||||
"smtp_password": ""
|
||||
}
|
BIN
data/dk.db
BIN
data/dk.db
Binary file not shown.
2
go.mod
2
go.mod
@ -5,7 +5,6 @@ go 1.25.0
|
||||
require (
|
||||
git.sharkk.net/Sharkk/Sashimi v1.1.4
|
||||
git.sharkk.net/Sharkk/Sushi v1.2.0
|
||||
github.com/valyala/fasthttp v1.65.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -17,6 +16,7 @@ require (
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.65.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
|
@ -25,15 +25,11 @@ func LeftAside(ctx sushi.Ctx) map[string]any {
|
||||
}
|
||||
|
||||
// Build owned town maps list
|
||||
if user.Towns != "" {
|
||||
townMap := helpers.NewOrderedMap[int, *towns.Town]()
|
||||
for _, id := range user.GetTownIDs() {
|
||||
if town, err := towns.Find(id); err == nil {
|
||||
townMap.Set(id, town)
|
||||
}
|
||||
}
|
||||
data["_towns"] = townMap.ToSlice()
|
||||
townList, err := user.GetKnownTowns()
|
||||
if err != nil {
|
||||
fmt.Printf("User town list error: %s\n", err.Error())
|
||||
}
|
||||
data["_towns"] = townList
|
||||
|
||||
return data
|
||||
}
|
||||
@ -65,16 +61,11 @@ func RightAside(ctx sushi.Ctx) map[string]any {
|
||||
}
|
||||
|
||||
// Build known healing spells list
|
||||
userSpells, err := user.GetSpells()
|
||||
if err == nil {
|
||||
spellMap := helpers.NewOrderedMap[int, string]()
|
||||
for _, spell := range userSpells {
|
||||
if spell.IsHeal() {
|
||||
spellMap.Set(spell.ID, spell.Name)
|
||||
}
|
||||
}
|
||||
data["_spells"] = spellMap.ToSlice()
|
||||
healingSpells, err := user.GetHealingSpells()
|
||||
if err != nil {
|
||||
fmt.Printf("User healing spells list error: %s\n", err.Error())
|
||||
}
|
||||
data["_spells"] = healingSpells
|
||||
|
||||
return data
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package exp
|
||||
|
||||
func Calc(level int) int {
|
||||
return level * level * level
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// IsHTTPS tries to determine if the current request context is over HTTPS
|
||||
func IsHTTPS(ctx *fasthttp.RequestCtx) bool {
|
||||
return ctx.IsTLS() ||
|
||||
string(ctx.Request.Header.Peek("X-Forwarded-Proto")) == "https" ||
|
||||
string(ctx.Request.Header.Peek("X-Forwarded-Scheme")) == "https"
|
||||
}
|
@ -8,3 +8,8 @@ func ClampPct(num, denom, minimum, maximum float64) float64 {
|
||||
}
|
||||
return max(minimum, min(maximum, (num/denom)*100))
|
||||
}
|
||||
|
||||
// ExpAtLevel calculates the needed total EXP for the given level.
|
||||
func ExpAtLevel(level int) int {
|
||||
return level * level * level
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
package helpers
|
||||
|
||||
type OrderedMap[K comparable, V any] struct {
|
||||
keys []K
|
||||
data map[K]V
|
||||
}
|
||||
|
||||
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
|
||||
return &OrderedMap[K, V]{
|
||||
keys: make([]K, 0),
|
||||
data: make(map[K]V),
|
||||
}
|
||||
}
|
||||
|
||||
func (om *OrderedMap[K, V]) Set(key K, value V) {
|
||||
if _, exists := om.data[key]; !exists {
|
||||
om.keys = append(om.keys, key)
|
||||
}
|
||||
om.data[key] = value
|
||||
}
|
||||
|
||||
func (om *OrderedMap[K, V]) Range(fn func(K, V) bool) {
|
||||
for _, key := range om.keys {
|
||||
if !fn(key, om.data[key]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (om *OrderedMap[K, V]) ToSlice() []V {
|
||||
result := make([]V, 0, len(om.keys))
|
||||
for _, key := range om.keys {
|
||||
result = append(result, om.data[key])
|
||||
}
|
||||
return result
|
||||
}
|
@ -116,8 +116,8 @@ func ByName(name string) (*Spell, error) {
|
||||
func UnlocksForClassAtLevel(classID, level int) ([]*Spell, error) {
|
||||
var spells []*Spell
|
||||
query := `
|
||||
SELECT s.* FROM spells s
|
||||
JOIN spell_unlocks u ON s.id = u.spell_id
|
||||
SELECT s.* FROM spells s
|
||||
JOIN spell_unlocks u ON s.id = u.spell_id
|
||||
WHERE u.class_id = %d AND u.level = %d
|
||||
ORDER BY s.type ASC, s.mp ASC, s.id ASC`
|
||||
err := database.Select(&spells, query, classID, level)
|
||||
@ -135,6 +135,17 @@ func UserSpells(userID int) ([]*Spell, error) {
|
||||
return spells, err
|
||||
}
|
||||
|
||||
func UserHealingSpells(userID int) ([]*Spell, error) {
|
||||
var spells []*Spell
|
||||
query := `
|
||||
SELECT s.* FROM spells s
|
||||
JOIN user_spells us ON s.id = us.spell_id
|
||||
WHERE us.user_id = %d AND s.type = 0
|
||||
ORDER BY s.mp ASC, s.id ASC`
|
||||
err := database.Select(&spells, query, userID)
|
||||
return spells, err
|
||||
}
|
||||
|
||||
func GrantSpell(userID, spellID int) error {
|
||||
return database.Exec("INSERT OR IGNORE INTO user_spells (user_id, spell_id) VALUES (%d, %d)", userID, spellID)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"dk/internal/database"
|
||||
"dk/internal/helpers"
|
||||
"dk/internal/models/items"
|
||||
)
|
||||
|
||||
// Town represents a town in the game
|
||||
@ -142,16 +143,16 @@ func ByDistance(fromX, fromY, maxDistance int) ([]*Town, error) {
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
func (t *Town) GetShopItems() []int {
|
||||
func (t *Town) GetShopItemIDs() []int {
|
||||
return helpers.StringToInts(t.ShopList)
|
||||
}
|
||||
|
||||
func (t *Town) SetShopItems(items []int) {
|
||||
func (t *Town) SetShopItemIDs(items []int) {
|
||||
t.ShopList = helpers.IntsToString(items)
|
||||
}
|
||||
|
||||
func (t *Town) HasShopItem(itemID int) bool {
|
||||
return slices.Contains(t.GetShopItems(), itemID)
|
||||
return slices.Contains(t.GetShopItemIDs(), itemID)
|
||||
}
|
||||
|
||||
func (t *Town) DistanceFromSquared(x, y int) float64 {
|
||||
@ -181,7 +182,7 @@ func (t *Town) CanAffordTeleport(gold int) bool {
|
||||
}
|
||||
|
||||
func (t *Town) HasShop() bool {
|
||||
return len(t.GetShopItems()) > 0
|
||||
return len(t.GetShopItemIDs()) > 0
|
||||
}
|
||||
|
||||
func (t *Town) GetPosition() (int, int) {
|
||||
@ -192,3 +193,19 @@ func (t *Town) SetPosition(x, y int) {
|
||||
t.X = x
|
||||
t.Y = y
|
||||
}
|
||||
|
||||
func (t *Town) GetShopItems() ([]*items.Item, error) {
|
||||
itemIDs := t.GetShopItemIDs()
|
||||
result := make([]*items.Item, 0, len(itemIDs))
|
||||
|
||||
for _, itemID := range itemIDs {
|
||||
item, err := items.Find(itemID)
|
||||
if err != nil {
|
||||
// Skip invalid item IDs rather than failing entirely
|
||||
continue
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
|
||||
"dk/internal/database"
|
||||
"dk/internal/helpers"
|
||||
"dk/internal/helpers/exp"
|
||||
"dk/internal/models/classes"
|
||||
"dk/internal/models/spells"
|
||||
"dk/internal/models/towns"
|
||||
)
|
||||
|
||||
// User represents a user in the game
|
||||
@ -213,6 +213,10 @@ func (u *User) GetSpells() ([]*spells.Spell, error) {
|
||||
return spells.UserSpells(u.ID)
|
||||
}
|
||||
|
||||
func (u *User) GetHealingSpells() ([]*spells.Spell, error) {
|
||||
return spells.UserHealingSpells(u.ID)
|
||||
}
|
||||
|
||||
func (u *User) HasSpell(spellID int) bool {
|
||||
return spells.HasSpell(u.ID, spellID)
|
||||
}
|
||||
@ -288,7 +292,7 @@ func (u *User) SetPosition(x, y int) {
|
||||
}
|
||||
|
||||
func (u *User) ExpNeededForNextLevel() int {
|
||||
return exp.Calc(u.Level + 1)
|
||||
return helpers.ExpAtLevel(u.Level + 1)
|
||||
}
|
||||
|
||||
func (u *User) GrantExp(expAmount int) map[string]any {
|
||||
@ -324,7 +328,7 @@ func (u *User) CalculateLevelUp(expGain int) (newLevel, newStr, newDex, newExp i
|
||||
totalExp := u.Exp + expGain
|
||||
|
||||
for {
|
||||
expNeeded := exp.Calc(level + 1)
|
||||
expNeeded := helpers.ExpAtLevel(level + 1)
|
||||
if totalExp < expNeeded {
|
||||
break
|
||||
}
|
||||
@ -343,7 +347,7 @@ func (u *User) ExpProgress() float64 {
|
||||
return float64(u.Exp) / float64(u.ExpNeededForNextLevel()) * 100
|
||||
}
|
||||
|
||||
currentLevelExp := exp.Calc(u.Level)
|
||||
currentLevelExp := helpers.ExpAtLevel(u.Level)
|
||||
nextLevelExp := u.ExpNeededForNextLevel()
|
||||
progressExp := u.Exp
|
||||
|
||||
@ -375,3 +379,19 @@ func (u *User) learnSpellsForLevel(level int) error {
|
||||
|
||||
return u.GrantSpells(spellIDs)
|
||||
}
|
||||
|
||||
func (u *User) GetKnownTowns() ([]*towns.Town, error) {
|
||||
townIDs := u.GetTownIDs()
|
||||
result := make([]*towns.Town, 0, len(townIDs))
|
||||
|
||||
for _, townID := range townIDs {
|
||||
town, err := towns.Find(townID)
|
||||
if err != nil {
|
||||
// Skip invalid town IDs rather than failing entirely
|
||||
continue
|
||||
}
|
||||
result = append(result, town)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"dk/internal/models/towns"
|
||||
"dk/internal/models/users"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
sushi "git.sharkk.net/Sharkk/Sushi"
|
||||
"git.sharkk.net/Sharkk/Sushi/auth"
|
||||
@ -131,20 +130,14 @@ func showShop(ctx sushi.Ctx) {
|
||||
|
||||
town := ctx.UserValue("town").(*towns.Town)
|
||||
|
||||
var itemlist []*items.Item
|
||||
if town.ShopList != "" {
|
||||
itemMap := helpers.NewOrderedMap[int, *items.Item]()
|
||||
for _, id := range town.GetShopItems() {
|
||||
if item, err := items.Find(id); err == nil {
|
||||
itemMap.Set(id, item)
|
||||
}
|
||||
}
|
||||
itemlist = itemMap.ToSlice()
|
||||
itemList, err := town.GetShopItems()
|
||||
if err != nil {
|
||||
fmt.Printf("Town shop list error: %s\n", err.Error())
|
||||
}
|
||||
|
||||
components.RenderPage(ctx, town.Name+" Shop", "town/shop.html", map[string]any{
|
||||
"town": town,
|
||||
"itemlist": itemlist,
|
||||
"itemlist": itemList,
|
||||
"error_message": errorHTML,
|
||||
})
|
||||
}
|
||||
@ -155,7 +148,7 @@ func buyItem(ctx sushi.Ctx) {
|
||||
id := ctx.Param("id").Int()
|
||||
|
||||
town := ctx.UserValue("town").(*towns.Town)
|
||||
if !slices.Contains(town.GetShopItems(), id) {
|
||||
if !town.HasShopItem(id) {
|
||||
sess.SetFlash("error", "The item doesn't exist in this shop.")
|
||||
ctx.Redirect("/town/shop")
|
||||
return
|
||||
@ -216,36 +209,12 @@ func showMaps(ctx sushi.Ctx) {
|
||||
}
|
||||
|
||||
town := ctx.UserValue("town").(*towns.Town)
|
||||
user := ctx.GetCurrentUser().(*users.User)
|
||||
|
||||
maplist := helpers.NewOrderedMap[int, Map]()
|
||||
towns, _ := towns.All()
|
||||
var mapped []int
|
||||
if user.Towns != "" {
|
||||
mapped = user.GetTownIDs()
|
||||
}
|
||||
for _, town := range towns {
|
||||
var owned bool
|
||||
if mapped != nil && slices.Contains(mapped, town.ID) {
|
||||
owned = true
|
||||
} else {
|
||||
owned = false
|
||||
}
|
||||
|
||||
maplist.Set(town.ID, Map{
|
||||
ID: town.ID,
|
||||
Name: town.Name,
|
||||
Cost: town.MapCost,
|
||||
Owned: owned,
|
||||
X: town.X,
|
||||
Y: town.Y,
|
||||
TP: town.TPCost,
|
||||
})
|
||||
}
|
||||
|
||||
components.RenderPage(ctx, town.Name+" Maps", "town/maps.html", map[string]any{
|
||||
"town": town,
|
||||
"maplist": maplist.ToSlice(),
|
||||
"towns": towns,
|
||||
"error_message": errorHTML,
|
||||
})
|
||||
}
|
||||
|
5
main.go
5
main.go
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"dk/internal/control"
|
||||
"dk/internal/database"
|
||||
"dk/internal/helpers/email"
|
||||
"dk/internal/helpers/markdown"
|
||||
"dk/internal/models/users"
|
||||
"dk/internal/routes"
|
||||
@ -142,10 +143,14 @@ func start(port string) error {
|
||||
|
||||
control.Init(filepath.Join(cwd, "data/control.json"))
|
||||
defer control.Save()
|
||||
controls := control.Get()
|
||||
|
||||
template.InitializeCache(cwd)
|
||||
template.RegisterFunc("md", markdown.MarkdownTemplateFunc)
|
||||
|
||||
email.Init(controls.EmailMode, controls.EmailFilePath,
|
||||
controls.SMTPHost, controls.SMTPPort, controls.SMTPUsername, controls.SMTPPassword)
|
||||
|
||||
authMW := auth.New(getUserByID)
|
||||
|
||||
app := sushi.New()
|
||||
|
@ -29,15 +29,17 @@
|
||||
<h5>Town Maps</h5>
|
||||
{if #_towns > 0}
|
||||
{for t in _towns}
|
||||
{if user.HasTownMap(t.ID)}
|
||||
{if town != nil and t.Name == town.Name}
|
||||
<span>{t.Name} <i>(here)</i></span>
|
||||
{else}
|
||||
{if user.TP < t.TPCost}
|
||||
<span class="light">{t.Name}</span>
|
||||
{else}
|
||||
<a class="teleport-link" href="/teleport/{t.ID}" data-town="{t.Name}" data-cost="{t.TPCost}">{t.Name}</a>
|
||||
<a class="teleport-link" href="/teleport/{t.ID}" data-town="{t.Name}" data-cost="{t.TPCost}">{t.Name}</a>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
{/for}
|
||||
{else}
|
||||
<i>No town maps</i>
|
||||
|
@ -76,8 +76,8 @@
|
||||
<section>
|
||||
<h5>Spells</h5>
|
||||
{if #_spells > 0}
|
||||
{for id,name in _spells}
|
||||
<a href="#">{name}</a>
|
||||
{for spell in _spells}
|
||||
<a href="#">{spell.Name}</a>
|
||||
{/for}
|
||||
{else}
|
||||
<i>No known healing spells</i>
|
||||
|
Loading…
x
Reference in New Issue
Block a user