diff --git a/data/dk.db b/data/dk.db index 09be7ff..c3fbe10 100644 Binary files a/data/dk.db and b/data/dk.db differ diff --git a/database.sql b/database.sql index c19acb0..de200a0 100644 --- a/database.sql +++ b/database.sql @@ -340,7 +340,7 @@ CREATE TABLE towns ( `shop_list` TEXT NOT NULL ); 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'), (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'), diff --git a/go.mod b/go.mod index cfa7860..960489a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module dk go 1.25.0 require ( - git.sharkk.net/Sharkk/Sushi v1.1.1 + git.sharkk.net/Sharkk/Sushi v1.2.0 github.com/valyala/fasthttp v1.65.0 zombiezen.com/go/sqlite v1.4.2 ) diff --git a/go.sum b/go.sum index 445de1f..a31e7bc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.sharkk.net/Sharkk/Sushi v1.1.1 h1:ynU16l6vAhY/JUwHlI4zMQiPuL9lcs88W/mAGZsL4Rw= -git.sharkk.net/Sharkk/Sushi v1.1.1/go.mod h1:S84ACGkuZ+BKzBO4lb5WQnm5aw9+l7VSO2T1bjzxL3o= +git.sharkk.net/Sharkk/Sushi v1.2.0 h1:RwOCZmgaOqtkmuK2Z7/esdLbhSXJZphsOsWEHni4Sss= +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/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= diff --git a/internal/actions/fight.go b/internal/actions/fight.go index 5de0f68..d1ce302 100644 --- a/internal/actions/fight.go +++ b/internal/actions/fight.go @@ -20,6 +20,38 @@ type FightResult struct { Ended bool Victory 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 { @@ -61,10 +93,7 @@ func HandleAttack(fight *fights.Fight, user *users.User) *FightResult { } finalDamage := int(damage) - newMonsterHP := fight.MonsterHP - finalDamage - if newMonsterHP < 0 { - newMonsterHP = 0 - } + newMonsterHP := max(fight.MonsterHP-finalDamage, 0) result := &FightResult{ FightUpdates: map[string]any{"monster_hp": newMonsterHP}, @@ -73,37 +102,13 @@ func HandleAttack(fight *fights.Fight, user *users.User) *FightResult { // Check if monster defeated if newMonsterHP <= 0 { - rewardGold, rewardExp := calculateRewards(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.EndFightWithVictory(monster, user) result.LogAction = func() error { if err := fightlogs.AddAttackHit(fight.ID, finalDamage); err != nil { return err } return fightlogs.AddMonsterDeath(fight.ID, monster.Name) } - result.Ended = true - result.Victory = true - result.Won = true } return result @@ -135,31 +140,20 @@ func HandleSpell(fight *fights.Fight, user *users.User, spellID int) *FightResul switch spell.Type { case spells.TypeHealing: - newHP := user.HP + spell.Attribute - if newHP > user.MaxHP { - newHP = user.MaxHP - } + newHP := min(user.HP+spell.Attribute, user.MaxHP) result.UserUpdates["hp"] = newHP result.LogAction = func() error { return fightlogs.AddSpellHeal(fight.ID, spell.Name, spell.Attribute) } case spells.TypeHurt: - newMonsterHP := fight.MonsterHP - spell.Attribute - if newMonsterHP < 0 { - newMonsterHP = 0 - } + newMonsterHP := max(fight.MonsterHP-spell.Attribute, 0) result.FightUpdates = map[string]any{"monster_hp": newMonsterHP} result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Attribute) } if newMonsterHP <= 0 { - result.FightUpdates["victory"] = true - result.FightUpdates["won"] = true - result.FightUpdates["reward_gold"] = 10 - result.FightUpdates["reward_exp"] = 5 - result.UserUpdates["fight_id"] = 0 - result.UserUpdates["currently"] = "Exploring" - result.Ended = true - result.Victory = true - result.Won = true + monster, err := monsters.Find(fight.MonsterID) + if err == nil { + result.EndFightWithVictory(monster, user) + } } default: @@ -218,10 +212,7 @@ func HandleMonsterAttack(fight *fights.Fight, user *users.User) *FightResult { } finalDamage := int(damage) - newHP := user.HP - finalDamage - if newHP < 0 { - newHP = 0 - } + newHP := max(user.HP-finalDamage, 0) result := &FightResult{ 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) { - 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 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 gold := rand.Intn(maxGold-minGold+1) + minGold + if gold < 1 { + gold = 1 // Guaranteed minimum + } + // Apply bonuses expBonus := (user.ExpBonus * exp) / 100 exp += expBonus diff --git a/internal/components/asides.go b/internal/components/asides.go index e41905f..7c62a4e 100644 --- a/internal/components/asides.go +++ b/internal/components/asides.go @@ -44,7 +44,7 @@ func RightAside(ctx sushi.Ctx) map[string]any { return data } - user := ctx.UserValue("user").(*users.User) + user := ctx.GetCurrentUser().(*users.User) data["_expprog"] = fmt.Sprintf("%.1f", user.ExpProgress()) diff --git a/internal/routes/auth.go b/internal/routes/auth.go index 53f3560..c30e1ca 100644 --- a/internal/routes/auth.go +++ b/internal/routes/auth.go @@ -95,46 +95,35 @@ func showRegister(ctx sushi.Ctx) { // processRegister handles registration form submission func processRegister(ctx sushi.Ctx) { - fmt.Println("DEBUG: Starting registration process") - username := strings.TrimSpace(ctx.Form("username").String()) email := strings.TrimSpace(ctx.Form("email").String()) userPassword := ctx.Form("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{ "username": username, "email": email, } if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil { - fmt.Printf("DEBUG: Validation failed: %v\n", err) setFlashAndFormData(ctx, err.Error(), formData) ctx.Redirect("/register") return } - fmt.Println("DEBUG: Validation passed") // Check if username already exists if _, err := users.ByUsername(username); err == nil { - fmt.Printf("DEBUG: Username '%s' already exists\n", username) setFlashAndFormData(ctx, "Username already exists", formData) ctx.Redirect("/register") return } - fmt.Printf("DEBUG: Username '%s' is available\n", username) // Check if email already exists if _, err := users.ByEmail(email); err == nil { - fmt.Printf("DEBUG: Email '%s' already exists\n", email) setFlashAndFormData(ctx, "Email already registered", formData) ctx.Redirect("/register") return } - fmt.Printf("DEBUG: Email '%s' is available\n", email) // Create new user user := users.New() @@ -144,34 +133,26 @@ func processRegister(ctx sushi.Ctx) { user.ClassID = 1 user.Auth = 1 - fmt.Printf("DEBUG: Created user struct with ID: %d\n", user.ID) - // Validate before inserting 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) ctx.Redirect("/register") return } - fmt.Println("DEBUG: User validation passed") if err := user.Insert(); err != nil { - fmt.Printf("DEBUG: User insert failed: %v\n", err) setFlashAndFormData(ctx, "Failed to create account", formData) ctx.Redirect("/register") return } - fmt.Printf("DEBUG: User inserted successfully with ID: %d\n", user.ID) // Auto-login after registration ctx.Login(user.ID, user) - fmt.Printf("DEBUG: User logged in with ID: %d\n", user.ID) // Set success message sess := ctx.GetCurrentSession() sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username)) - fmt.Println("DEBUG: Registration completed successfully") ctx.Redirect("/") } diff --git a/internal/routes/fight.go b/internal/routes/fight.go index fe898a3..451f41c 100644 --- a/internal/routes/fight.go +++ b/internal/routes/fight.go @@ -154,7 +154,7 @@ func handleFightAction(ctx sushi.Ctx) { // Handle fight end states if result.Ended { 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) } else if result.Victory { sess.SetFlash("error", "You have been defeated! You lost some gold and were sent to the nearest town.") diff --git a/internal/routes/index.go b/internal/routes/index.go index dd21652..5354fb0 100644 --- a/internal/routes/index.go +++ b/internal/routes/index.go @@ -54,12 +54,18 @@ func Move(ctx sushi.Ctx) { return } + updateData := map[string]any{ + "currently": currently, + "x": newX, + "y": newY, + } + + if currently == "Fighting" { + updateData["fight_id"] = user.FightID + } + err = database.Transaction(func() error { - return database.Update("users", map[string]any{ - "currently": currently, - "x": newX, - "y": newY, - }, "id", user.ID) + return database.Update("users", updateData, "id", user.ID) }) if err != nil { diff --git a/internal/routes/town.go b/internal/routes/town.go index 17abeee..d01f9f2 100644 --- a/internal/routes/town.go +++ b/internal/routes/town.go @@ -77,9 +77,17 @@ func showTown(ctx sushi.Ctx) { func showInn(ctx sushi.Ctx) { 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{ "town": town, - "rested": false, + "rested": rested, }) } @@ -102,17 +110,14 @@ func rest(ctx sushi.Ctx) { "tp": user.MaxTP, }, "id", user.ID) }) - if err != nil { sess.SetFlash("error", "Failed to rest at inn.") ctx.Redirect("/town/inn") return } - components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{ - "town": town, - "rested": true, - }) + sess.SetFlash("rested", true) + ctx.Redirect("/town/inn") } func showShop(ctx sushi.Ctx) { diff --git a/main.go b/main.go index 1987c2a..5f5cce5 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os/signal" "path/filepath" "syscall" + "time" "dk/internal/control" "dk/internal/database" @@ -68,13 +69,27 @@ func start(port string) error { template.InitializeCache(cwd) + authMW := auth.New(getUserByID) + app := sushi.New() sushi.InitSessions(filepath.Join(cwd, "data/sessions.json")) app.Use(session.Middleware()) - app.Use(auth.Middleware(getUserByID)) + app.Use(authMW.Middleware()) app.Use(csrf.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)