fix lots of little bugs and get last_online working

This commit is contained in:
Sky Johnson 2025-08-22 09:09:15 -05:00
parent ee19412965
commit cfc1256d02
11 changed files with 95 additions and 89 deletions

Binary file not shown.

View File

@ -340,7 +340,7 @@ CREATE TABLE towns (
`shop_list` TEXT NOT NULL `shop_list` TEXT NOT NULL
); );
INSERT INTO towns VALUES INSERT INTO towns VALUES
(1, 'Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19,28,29'), (1, 'Midworld', 0, 0, 1, 0, 0, '1,2,3,17,18,19,28,29'),
(2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'), (2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'),
(3, 'Bris', 70, -70, 25, 50, 15, '2,3,4,5,18,19,20,29.30'), (3, 'Bris', 70, -70, 25, 50, 15, '2,3,4,5,18,19,20,29.30'),
(4, 'Kalle', -100, 100, 40, 100, 30, '5,6,8,10,12,21,22,23,29,30'), (4, 'Kalle', -100, 100, 40, 100, 30, '5,6,8,10,12,21,22,23,29,30'),

2
go.mod
View File

@ -3,7 +3,7 @@ module dk
go 1.25.0 go 1.25.0
require ( require (
git.sharkk.net/Sharkk/Sushi v1.1.1 git.sharkk.net/Sharkk/Sushi v1.2.0
github.com/valyala/fasthttp v1.65.0 github.com/valyala/fasthttp v1.65.0
zombiezen.com/go/sqlite v1.4.2 zombiezen.com/go/sqlite v1.4.2
) )

4
go.sum
View File

@ -1,5 +1,5 @@
git.sharkk.net/Sharkk/Sushi v1.1.1 h1:ynU16l6vAhY/JUwHlI4zMQiPuL9lcs88W/mAGZsL4Rw= git.sharkk.net/Sharkk/Sushi v1.2.0 h1:RwOCZmgaOqtkmuK2Z7/esdLbhSXJZphsOsWEHni4Sss=
git.sharkk.net/Sharkk/Sushi v1.1.1/go.mod h1:S84ACGkuZ+BKzBO4lb5WQnm5aw9+l7VSO2T1bjzxL3o= git.sharkk.net/Sharkk/Sushi v1.2.0/go.mod h1:S84ACGkuZ+BKzBO4lb5WQnm5aw9+l7VSO2T1bjzxL3o=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=

View File

@ -20,6 +20,38 @@ type FightResult struct {
Ended bool Ended bool
Victory bool Victory bool
Won bool Won bool
RewardGold int
RewardExp int
}
func (r *FightResult) EndFightWithVictory(monster *monsters.Monster, user *users.User) {
rewardGold, rewardExp := calculateRewards(monster, user)
r.FightUpdates["victory"] = true
r.FightUpdates["won"] = true
r.FightUpdates["reward_gold"] = rewardGold
r.FightUpdates["reward_exp"] = rewardExp
r.UserUpdates = map[string]any{
"fight_id": 0,
"currently": "Exploring",
"gold": user.Gold + rewardGold,
"exp": user.Exp + rewardExp,
}
// Handle level up
newLevel, newStats := calculateLevelUp(user.Level, user.Exp+rewardExp, user.Strength, user.Dexterity)
if newLevel > user.Level {
r.UserUpdates["level"] = newLevel
r.UserUpdates["strength"] = newStats.Strength
r.UserUpdates["dexterity"] = newStats.Dexterity
}
r.Ended = true
r.Victory = true
r.Won = true
r.RewardGold = rewardGold
r.RewardExp = rewardExp
} }
func HandleAttack(fight *fights.Fight, user *users.User) *FightResult { func HandleAttack(fight *fights.Fight, user *users.User) *FightResult {
@ -61,10 +93,7 @@ func HandleAttack(fight *fights.Fight, user *users.User) *FightResult {
} }
finalDamage := int(damage) finalDamage := int(damage)
newMonsterHP := fight.MonsterHP - finalDamage newMonsterHP := max(fight.MonsterHP-finalDamage, 0)
if newMonsterHP < 0 {
newMonsterHP = 0
}
result := &FightResult{ result := &FightResult{
FightUpdates: map[string]any{"monster_hp": newMonsterHP}, FightUpdates: map[string]any{"monster_hp": newMonsterHP},
@ -73,37 +102,13 @@ func HandleAttack(fight *fights.Fight, user *users.User) *FightResult {
// Check if monster defeated // Check if monster defeated
if newMonsterHP <= 0 { if newMonsterHP <= 0 {
rewardGold, rewardExp := calculateRewards(monster, user) result.EndFightWithVictory(monster, user)
result.FightUpdates["victory"] = true
result.FightUpdates["won"] = true
result.FightUpdates["reward_gold"] = rewardGold
result.FightUpdates["reward_exp"] = rewardExp
result.UserUpdates = map[string]any{
"fight_id": 0,
"currently": "Exploring",
"gold": user.Gold + rewardGold,
"exp": user.Exp + rewardExp,
}
// Handle level up
newLevel, newStats := calculateLevelUp(user.Level, user.Exp+rewardExp, user.Strength, user.Dexterity)
if newLevel > user.Level {
result.UserUpdates["level"] = newLevel
result.UserUpdates["strength"] = newStats.Strength
result.UserUpdates["dexterity"] = newStats.Dexterity
}
result.LogAction = func() error { result.LogAction = func() error {
if err := fightlogs.AddAttackHit(fight.ID, finalDamage); err != nil { if err := fightlogs.AddAttackHit(fight.ID, finalDamage); err != nil {
return err return err
} }
return fightlogs.AddMonsterDeath(fight.ID, monster.Name) return fightlogs.AddMonsterDeath(fight.ID, monster.Name)
} }
result.Ended = true
result.Victory = true
result.Won = true
} }
return result return result
@ -135,31 +140,20 @@ func HandleSpell(fight *fights.Fight, user *users.User, spellID int) *FightResul
switch spell.Type { switch spell.Type {
case spells.TypeHealing: case spells.TypeHealing:
newHP := user.HP + spell.Attribute newHP := min(user.HP+spell.Attribute, user.MaxHP)
if newHP > user.MaxHP {
newHP = user.MaxHP
}
result.UserUpdates["hp"] = newHP result.UserUpdates["hp"] = newHP
result.LogAction = func() error { return fightlogs.AddSpellHeal(fight.ID, spell.Name, spell.Attribute) } result.LogAction = func() error { return fightlogs.AddSpellHeal(fight.ID, spell.Name, spell.Attribute) }
case spells.TypeHurt: case spells.TypeHurt:
newMonsterHP := fight.MonsterHP - spell.Attribute newMonsterHP := max(fight.MonsterHP-spell.Attribute, 0)
if newMonsterHP < 0 {
newMonsterHP = 0
}
result.FightUpdates = map[string]any{"monster_hp": newMonsterHP} result.FightUpdates = map[string]any{"monster_hp": newMonsterHP}
result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Attribute) } result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Attribute) }
if newMonsterHP <= 0 { if newMonsterHP <= 0 {
result.FightUpdates["victory"] = true monster, err := monsters.Find(fight.MonsterID)
result.FightUpdates["won"] = true if err == nil {
result.FightUpdates["reward_gold"] = 10 result.EndFightWithVictory(monster, user)
result.FightUpdates["reward_exp"] = 5 }
result.UserUpdates["fight_id"] = 0
result.UserUpdates["currently"] = "Exploring"
result.Ended = true
result.Victory = true
result.Won = true
} }
default: default:
@ -218,10 +212,7 @@ func HandleMonsterAttack(fight *fights.Fight, user *users.User) *FightResult {
} }
finalDamage := int(damage) finalDamage := int(damage)
newHP := user.HP - finalDamage newHP := max(user.HP-finalDamage, 0)
if newHP < 0 {
newHP = 0
}
result := &FightResult{ result := &FightResult{
UserUpdates: map[string]any{"hp": newHP}, UserUpdates: map[string]any{"hp": newHP},
@ -301,14 +292,22 @@ func findClosestTown(x, y int) *towns.Town {
} }
func calculateRewards(monster *monsters.Monster, user *users.User) (int, int) { func calculateRewards(monster *monsters.Monster, user *users.User) (int, int) {
minExp := (monster.MaxExp * 5) / 6 // More generous reward range: 90-100% of max instead of 83-100%
minExp := (monster.MaxExp * 9) / 10
maxExp := monster.MaxExp maxExp := monster.MaxExp
exp := rand.Intn(maxExp-minExp+1) + minExp exp := rand.Intn(maxExp-minExp+1) + minExp
if exp < 1 {
exp = 1 // Guaranteed minimum
}
minGold := (monster.MaxGold * 5) / 6 minGold := (monster.MaxGold * 9) / 10
maxGold := monster.MaxGold maxGold := monster.MaxGold
gold := rand.Intn(maxGold-minGold+1) + minGold gold := rand.Intn(maxGold-minGold+1) + minGold
if gold < 1 {
gold = 1 // Guaranteed minimum
}
// Apply bonuses
expBonus := (user.ExpBonus * exp) / 100 expBonus := (user.ExpBonus * exp) / 100
exp += expBonus exp += expBonus

View File

@ -44,7 +44,7 @@ func RightAside(ctx sushi.Ctx) map[string]any {
return data return data
} }
user := ctx.UserValue("user").(*users.User) user := ctx.GetCurrentUser().(*users.User)
data["_expprog"] = fmt.Sprintf("%.1f", user.ExpProgress()) data["_expprog"] = fmt.Sprintf("%.1f", user.ExpProgress())

View File

@ -95,46 +95,35 @@ func showRegister(ctx sushi.Ctx) {
// processRegister handles registration form submission // processRegister handles registration form submission
func processRegister(ctx sushi.Ctx) { func processRegister(ctx sushi.Ctx) {
fmt.Println("DEBUG: Starting registration process")
username := strings.TrimSpace(ctx.Form("username").String()) username := strings.TrimSpace(ctx.Form("username").String())
email := strings.TrimSpace(ctx.Form("email").String()) email := strings.TrimSpace(ctx.Form("email").String())
userPassword := ctx.Form("password").String() userPassword := ctx.Form("password").String()
confirmPassword := ctx.Form("confirm_password").String() confirmPassword := ctx.Form("confirm_password").String()
fmt.Printf("DEBUG: Form data - username: '%s', email: '%s', password length: %d\n",
username, email, len(userPassword))
formData := map[string]string{ formData := map[string]string{
"username": username, "username": username,
"email": email, "email": email,
} }
if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil { if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil {
fmt.Printf("DEBUG: Validation failed: %v\n", err)
setFlashAndFormData(ctx, err.Error(), formData) setFlashAndFormData(ctx, err.Error(), formData)
ctx.Redirect("/register") ctx.Redirect("/register")
return return
} }
fmt.Println("DEBUG: Validation passed")
// Check if username already exists // Check if username already exists
if _, err := users.ByUsername(username); err == nil { if _, err := users.ByUsername(username); err == nil {
fmt.Printf("DEBUG: Username '%s' already exists\n", username)
setFlashAndFormData(ctx, "Username already exists", formData) setFlashAndFormData(ctx, "Username already exists", formData)
ctx.Redirect("/register") ctx.Redirect("/register")
return return
} }
fmt.Printf("DEBUG: Username '%s' is available\n", username)
// Check if email already exists // Check if email already exists
if _, err := users.ByEmail(email); err == nil { if _, err := users.ByEmail(email); err == nil {
fmt.Printf("DEBUG: Email '%s' already exists\n", email)
setFlashAndFormData(ctx, "Email already registered", formData) setFlashAndFormData(ctx, "Email already registered", formData)
ctx.Redirect("/register") ctx.Redirect("/register")
return return
} }
fmt.Printf("DEBUG: Email '%s' is available\n", email)
// Create new user // Create new user
user := users.New() user := users.New()
@ -144,34 +133,26 @@ func processRegister(ctx sushi.Ctx) {
user.ClassID = 1 user.ClassID = 1
user.Auth = 1 user.Auth = 1
fmt.Printf("DEBUG: Created user struct with ID: %d\n", user.ID)
// Validate before inserting // Validate before inserting
if err := user.Validate(); err != nil { if err := user.Validate(); err != nil {
fmt.Printf("DEBUG: User validation failed: %v\n", err)
setFlashAndFormData(ctx, fmt.Sprintf("Invalid user data: %s", err.Error()), formData) setFlashAndFormData(ctx, fmt.Sprintf("Invalid user data: %s", err.Error()), formData)
ctx.Redirect("/register") ctx.Redirect("/register")
return return
} }
fmt.Println("DEBUG: User validation passed")
if err := user.Insert(); err != nil { if err := user.Insert(); err != nil {
fmt.Printf("DEBUG: User insert failed: %v\n", err)
setFlashAndFormData(ctx, "Failed to create account", formData) setFlashAndFormData(ctx, "Failed to create account", formData)
ctx.Redirect("/register") ctx.Redirect("/register")
return return
} }
fmt.Printf("DEBUG: User inserted successfully with ID: %d\n", user.ID)
// Auto-login after registration // Auto-login after registration
ctx.Login(user.ID, user) ctx.Login(user.ID, user)
fmt.Printf("DEBUG: User logged in with ID: %d\n", user.ID)
// Set success message // Set success message
sess := ctx.GetCurrentSession() sess := ctx.GetCurrentSession()
sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username)) sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username))
fmt.Println("DEBUG: Registration completed successfully")
ctx.Redirect("/") ctx.Redirect("/")
} }

View File

@ -154,7 +154,7 @@ func handleFightAction(ctx sushi.Ctx) {
// Handle fight end states // Handle fight end states
if result.Ended { if result.Ended {
if result.Won { if result.Won {
sess.SetFlash("success", fmt.Sprintf("Victory! You gained rewards!")) sess.SetFlash("success", fmt.Sprintf("Victory! You gained %d gold and %d experience!", result.RewardGold, result.RewardExp))
ctx.Redirect("/explore", 302) ctx.Redirect("/explore", 302)
} else if result.Victory { } else if result.Victory {
sess.SetFlash("error", "You have been defeated! You lost some gold and were sent to the nearest town.") sess.SetFlash("error", "You have been defeated! You lost some gold and were sent to the nearest town.")

View File

@ -54,12 +54,18 @@ func Move(ctx sushi.Ctx) {
return return
} }
updateData := map[string]any{
"currently": currently,
"x": newX,
"y": newY,
}
if currently == "Fighting" {
updateData["fight_id"] = user.FightID
}
err = database.Transaction(func() error { err = database.Transaction(func() error {
return database.Update("users", map[string]any{ return database.Update("users", updateData, "id", user.ID)
"currently": currently,
"x": newX,
"y": newY,
}, "id", user.ID)
}) })
if err != nil { if err != nil {

View File

@ -77,9 +77,17 @@ func showTown(ctx sushi.Ctx) {
func showInn(ctx sushi.Ctx) { func showInn(ctx sushi.Ctx) {
town := ctx.UserValue("town").(*towns.Town) town := ctx.UserValue("town").(*towns.Town)
sess := ctx.GetCurrentSession()
value, exists := sess.GetFlash("rested")
rested := false
if exists && value == true {
rested = true
}
components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{ components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{
"town": town, "town": town,
"rested": false, "rested": rested,
}) })
} }
@ -102,17 +110,14 @@ func rest(ctx sushi.Ctx) {
"tp": user.MaxTP, "tp": user.MaxTP,
}, "id", user.ID) }, "id", user.ID)
}) })
if err != nil { if err != nil {
sess.SetFlash("error", "Failed to rest at inn.") sess.SetFlash("error", "Failed to rest at inn.")
ctx.Redirect("/town/inn") ctx.Redirect("/town/inn")
return return
} }
components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{ sess.SetFlash("rested", true)
"town": town, ctx.Redirect("/town/inn")
"rested": true,
})
} }
func showShop(ctx sushi.Ctx) { func showShop(ctx sushi.Ctx) {

17
main.go
View File

@ -8,6 +8,7 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"syscall" "syscall"
"time"
"dk/internal/control" "dk/internal/control"
"dk/internal/database" "dk/internal/database"
@ -68,13 +69,27 @@ func start(port string) error {
template.InitializeCache(cwd) template.InitializeCache(cwd)
authMW := auth.New(getUserByID)
app := sushi.New() app := sushi.New()
sushi.InitSessions(filepath.Join(cwd, "data/sessions.json")) sushi.InitSessions(filepath.Join(cwd, "data/sessions.json"))
app.Use(session.Middleware()) app.Use(session.Middleware())
app.Use(auth.Middleware(getUserByID)) app.Use(authMW.Middleware())
app.Use(csrf.Middleware()) app.Use(csrf.Middleware())
app.Use(timing.Middleware()) app.Use(timing.Middleware())
app.Use(func(ctx sushi.Ctx, next func()) {
if ctx.IsAuthenticated() {
user := ctx.GetCurrentUser().(*users.User)
now := time.Now().Unix()
if (now - user.LastOnline) >= 540 { // 540 seconds = 9 minutes
database.Update("users", map[string]any{
"last_online": now,
}, "id", user.ID)
}
}
next()
})
app.Get("/", routes.Index) app.Get("/", routes.Index)