package routes import ( "dk/internal/actions" "dk/internal/components" "dk/internal/database" "dk/internal/helpers" "dk/internal/models/items" "dk/internal/models/towns" "dk/internal/models/users" "fmt" "slices" sushi "git.sharkk.net/Sharkk/Sushi" "git.sharkk.net/Sharkk/Sushi/auth" ) // Map acts as a representation of owned/unowned maps in the town stores. type Map struct { ID int Name string Cost int Owned bool X int Y int TP int } func RegisterTownRoutes(app *sushi.App) { group := app.Group("/town") group.Use(auth.RequireAuth("/login")) group.Use(requireTown()) group.Get("/", showTown) group.Get("/inn", showInn) group.Post("/inn", rest) group.Get("/shop", showShop) group.Get("/shop/buy/:id", buyItem) group.Get("/maps", showMaps) group.Get("/maps/buy/:id", buyMap) } // requireTown middleware ensures the user is in a town func requireTown() 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.Currently != "In Town" { ctx.SendError(403, "You must be in town") return } town, err := towns.ByCoords(userModel.X, userModel.Y) if err != nil || town == nil || town.ID == 0 { ctx.SendError(403, fmt.Sprintf("Invalid town location (%d, %d)", userModel.X, userModel.Y)) return } ctx.SetUserValue("town", town) next() } } func showTown(ctx sushi.Ctx) { town := ctx.UserValue("town").(*towns.Town) components.RenderPage(ctx, town.Name, "town/town.html", map[string]any{ "town": town, "newscontent": components.GenerateTownNews(), "whosonline": components.GenerateTownWhosOnline(), }) } 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": rested, }) } func rest(ctx sushi.Ctx) { sess := ctx.GetCurrentSession() town := ctx.UserValue("town").(*towns.Town) user := ctx.GetCurrentUser().(*users.User) if user.Gold < town.InnCost { sess.SetFlash("error", "You can't afford to stay here tonight.") ctx.Redirect("/town/inn") return } err := database.Transaction(func() error { return database.Update("users", map[string]any{ "gold": user.Gold - town.InnCost, "hp": user.MaxHP, "mp": user.MaxMP, "tp": user.MaxTP, }, "id", user.ID) }) if err != nil { sess.SetFlash("error", "Failed to rest at inn.") ctx.Redirect("/town/inn") return } sess.SetFlash("rested", true) ctx.Redirect("/town/inn") } func showShop(ctx sushi.Ctx) { sess := ctx.GetCurrentSession() var errorHTML string errorMsg := sess.GetFlashMessage("error") if errorMsg != "" { errorHTML = `
` + errorMsg + "
" } 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() } components.RenderPage(ctx, town.Name+" Shop", "town/shop.html", map[string]any{ "town": town, "itemlist": itemlist, "error_message": errorHTML, }) } func buyItem(ctx sushi.Ctx) { sess := ctx.GetCurrentSession() id := ctx.Param("id").Int() town := ctx.UserValue("town").(*towns.Town) if !slices.Contains(town.GetShopItems(), id) { sess.SetFlash("error", "The item doesn't exist in this shop.") ctx.Redirect("/town/shop") return } item, err := items.Find(id) if err != nil { sess.SetFlash("error", "Error purchasing item; "+err.Error()) ctx.Redirect("/town/shop") return } user := ctx.GetCurrentUser().(*users.User) if user.Gold < item.Value { sess.SetFlash("error", "You don't have enough gold to buy "+item.Name) ctx.Redirect("/town/shop") return } // Get equipment updates from actions equipUpdates, err := actions.UserEquipItem(user, item) if err != nil { sess.SetFlash("error", "Cannot equip item: "+err.Error()) ctx.Redirect("/town/shop") return } err = database.Transaction(func() error { // Start with gold deduction updates := map[string]any{ "gold": user.Gold - item.Value, } // Add equipment updates for field, value := range equipUpdates { updates[field] = value } return database.Update("users", updates, "id", user.ID) }) if err != nil { sess.SetFlash("error", "Failed to purchase item.") ctx.Redirect("/town/shop") return } ctx.Redirect("/town/shop") } func showMaps(ctx sushi.Ctx) { sess := ctx.GetCurrentSession() var errorHTML string errorMsg := sess.GetFlashMessage("error") if errorMsg != "" { errorHTML = `
` + errorMsg + "
" } 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(), "error_message": errorHTML, }) } func buyMap(ctx sushi.Ctx) { sess := ctx.GetCurrentSession() id := ctx.Param("id").Int() mapped, err := towns.Find(id) if err != nil { sess.SetFlash("error", "Error purchasing map; "+err.Error()) ctx.Redirect("/town/maps") return } user := ctx.GetCurrentUser().(*users.User) if user.Gold < mapped.MapCost { sess.SetFlash("error", "You don't have enough gold to buy the map to "+mapped.Name) ctx.Redirect("/town/maps") return } townIDs := user.GetTownIDs() townIDs = append(townIDs, id) newTownsString := helpers.IntsToString(townIDs) err = database.Transaction(func() error { return database.Update("users", map[string]any{ "gold": user.Gold - mapped.MapCost, "towns": newTownsString, }, "id", user.ID) }) if err != nil { sess.SetFlash("error", "Failed to purchase map.") ctx.Redirect("/town/maps") return } ctx.Redirect("/town/maps") }