package routes import ( "dk/internal/actions" "dk/internal/components" "dk/internal/database" "dk/internal/helpers" "dk/internal/models/fightlogs" "dk/internal/models/fights" "dk/internal/models/monsters" "dk/internal/models/spells" "dk/internal/models/users" "fmt" "math/rand" "strconv" sushi "git.sharkk.net/Sharkk/Sushi" "git.sharkk.net/Sharkk/Sushi/auth" ) func RegisterFightRoutes(app *sushi.App) { group := app.Group("/fight") group.Use(auth.RequireAuth("/login")) group.Use(requireFighting()) group.Get("/", showFight) group.Post("/", handleFightAction) } func requireFighting() sushi.Middleware { return func(ctx sushi.Ctx, next func()) { user := ctx.GetCurrentUser() if user == nil { ctx.SendError(401, "Not authenticated") return } userModel := user.(*users.User) if !userModel.IsFighting() { ctx.Redirect("/") return } next() } } func showFight(ctx sushi.Ctx) { sess := ctx.GetCurrentSession() user := ctx.GetCurrentUser().(*users.User) fight, err := fights.Find(user.FightID) if err != nil { ctx.SendError(404, "Fight not found") return } monster, err := monsters.Find(fight.MonsterID) if err != nil { ctx.SendError(404, "Monster not found for fight") return } // Initialize fight on first view if fight.Turn == 0 { err := database.Transaction(func() error { return database.Update("fights", map[string]any{ "first_strike": rand.Float32() < 0.5, "turn": 1, "monster_hp": monster.MaxHP, "monster_max_hp": monster.MaxHP, }, "id", fight.ID) }) if err != nil { ctx.SendError(500, "Failed to initialize fight") return } fight.FirstStrike = rand.Float32() < 0.5 fight.Turn = 1 fight.MonsterHP = monster.MaxHP fight.MonsterMaxHP = monster.MaxHP } monHpPct := helpers.ClampPct(float64(fight.MonsterHP), float64(fight.MonsterMaxHP), 0, 100) monHpColor := "" if monHpPct < 35 { monHpColor = "danger" } else if monHpPct < 75 { monHpColor = "warning" } var userSpells []*spells.Spell spellList, err := user.GetSpells() if err == nil { userSpells = spellList } // Get recent fight actions lastAction, _ := fightlogs.GetLastAction(fight.ID) components.RenderPage(ctx, "Fighting", "fight/fight.html", map[string]any{ "fight": fight, "user": user, "monster": monster, "mon_hppct": monHpPct, "mon_hpcol": monHpColor, "spells": userSpells, "action": sess.GetFlashMessage("action"), "mon_action": sess.GetFlashMessage("mon_action"), "last_action": lastAction, }) } func handleFightAction(ctx sushi.Ctx) { sess := ctx.GetCurrentSession() user := ctx.GetCurrentUser().(*users.User) fight, err := fights.Find(user.FightID) if err != nil { ctx.SetContentType("text/plain") ctx.SetBodyString("Fight not found") return } action := string(ctx.FormValue("action")) var result *actions.FightResult switch action { case "attack": result = actions.HandleAttack(fight, user) case "spell": spellIDStr := string(ctx.FormValue("spell_id")) if spellID, err := strconv.Atoi(spellIDStr); err == nil { result = actions.HandleSpell(fight, user, spellID) } else { result = &actions.FightResult{ LogAction: func() error { return fightlogs.AddAction(fight.ID, "Invalid spell!") }, ActionText: "Invalid spell!", } } case "run": result = actions.HandleRun(fight, user) default: result = &actions.FightResult{ LogAction: func() error { return fightlogs.AddAction(fight.ID, "Invalid action!") }, ActionText: "Invalid action!", } } // Execute the action err = actions.ExecuteFightAction(fight.ID, result) if err != nil { ctx.SendError(500, "Failed to execute fight action") return } // Flash player action if result.ActionText != "" { sess.SetFlash("action", result.ActionText) } // Handle fight end states if result.Ended { if result.Won { 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.") ctx.Redirect("/town", 302) } else { // Ran away sess.SetFlash("success", "You successfully escaped!") ctx.Redirect("/explore", 302) } return } // Monster attacks back if fight continues if fight.IsActive() && user.HP > 0 { // Refresh user data after player action user, err = users.Find(user.ID) if err != nil { ctx.SendError(500, "Failed to refresh user data") return } monsterResult := actions.HandleMonsterAttack(fight, user) // Execute monster action err = actions.ExecuteFightAction(fight.ID, monsterResult) if err != nil { ctx.SendError(500, "Failed to execute monster action") return } // Flash monster action if monsterResult.ActionText != "" { sess.SetFlash("mon_action", monsterResult.ActionText) } // Check if monster action ended fight if monsterResult.Ended { if monsterResult.Won { sess.SetFlash("success", "Victory!") ctx.Redirect("/explore", 302) } else { sess.SetFlash("error", "You have been defeated!") ctx.Redirect("/town", 302) } return } } // Increment turn err = database.Transaction(func() error { return database.Update("fights", map[string]any{ "turn": fight.Turn + 1, }, "id", fight.ID) }) if err != nil { ctx.SendError(500, "Failed to increment turn") return } ctx.Redirect("/fight", 302) }