From 674cfce506c81c67fadb28e870faa1e6f5995a02 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Thu, 14 Aug 2025 19:05:35 -0500 Subject: [PATCH] create fights, disable compass --- assets/dk.css | 7 ++++ data/fights.json | 65 +++++++++++++++++++++++++++++++ internal/actions/move.go | 66 ++++++++++++++++++++++++++++++++ internal/middleware/fights.go | 59 ++++++++++++++++++++++++++++ internal/models/fights/fights.go | 6 +-- internal/routes/auth.go | 10 +---- internal/routes/fight.go | 43 +++++++++++++++++++++ internal/routes/index.go | 21 ++++++++-- internal/routes/town.go | 15 +------- main.go | 1 + templates/fight/fight.html | 5 +++ templates/leftside.html | 22 ++++++----- 12 files changed, 282 insertions(+), 38 deletions(-) create mode 100644 data/fights.json create mode 100644 internal/middleware/fights.go create mode 100644 internal/routes/fight.go create mode 100644 templates/fight/fight.html diff --git a/assets/dk.css b/assets/dk.css index 471bc23..d7a5f36 100644 --- a/assets/dk.css +++ b/assets/dk.css @@ -395,3 +395,10 @@ div.modal { .mt-1 { margin-top: 1rem; } + +img#move-compass-disabled { + margin: 0px auto; + cursor: not-allowed; + filter: grayscale(); + margin-top: 0.5rem; +} diff --git a/data/fights.json b/data/fights.json new file mode 100644 index 0000000..3d4360c --- /dev/null +++ b/data/fights.json @@ -0,0 +1,65 @@ +[ + { + "id": 1, + "user_id": 1, + "monster_id": 10, + "monster_hp": 16, + "monster_max_hp": 16, + "monster_sleep": 0, + "monster_immune": 0, + "uber_damage": 0, + "uber_defense": 0, + "first_strike": false, + "turn": 1, + "ran_away": false, + "victory": false, + "won": false, + "reward_gold": 0, + "reward_exp": 0, + "actions": [], + "created": 1755215734, + "updated": 1755215734 + }, + { + "id": 2, + "user_id": 1, + "monster_id": 7, + "monster_hp": 12, + "monster_max_hp": 12, + "monster_sleep": 0, + "monster_immune": 1, + "uber_damage": 0, + "uber_defense": 0, + "first_strike": true, + "turn": 1, + "ran_away": false, + "victory": false, + "won": false, + "reward_gold": 0, + "reward_exp": 0, + "actions": [], + "created": 1755215776, + "updated": 1755215776 + }, + { + "id": 3, + "user_id": 1, + "monster_id": 8, + "monster_hp": 14, + "monster_max_hp": 14, + "monster_sleep": 0, + "monster_immune": 0, + "uber_damage": 0, + "uber_defense": 0, + "first_strike": true, + "turn": 1, + "ran_away": false, + "victory": false, + "won": false, + "reward_gold": 0, + "reward_exp": 0, + "actions": [], + "created": 1755215777, + "updated": 1755215777 + } +] \ No newline at end of file diff --git a/internal/actions/move.go b/internal/actions/move.go index ea5c091..5eca3f1 100644 --- a/internal/actions/move.go +++ b/internal/actions/move.go @@ -2,9 +2,13 @@ package actions import ( "dk/internal/models/control" + "dk/internal/models/fights" + "dk/internal/models/monsters" "dk/internal/models/towns" "dk/internal/models/users" "fmt" + "math" + "math/rand" ) type Direction int @@ -54,5 +58,67 @@ func Move(user *users.User, dir Direction) (string, int, int, error) { return "In Town", newX, newY, nil } + // 33% chance to start a fight when moving to non-town location + if rand.Float32() < 0.33 { + fmt.Println("Fight chance!") + monster, err := getRandomMonsterByDistance(newX, newY) + if err != nil { + fmt.Printf("Finding a monster failed: %s\n", err.Error()) + return "Exploring", newX, newY, nil // Fall back to exploring if monster selection fails + } + + // Create new fight record + fight := fights.New(user.ID, monster.ID) + fight.MonsterHP = monster.MaxHP + fight.MonsterMaxHP = monster.MaxHP + fight.MonsterImmune = monster.Immune + + err = fight.Insert() + if err != nil { + fmt.Printf("Inserting a fight failed: %s\n", err.Error()) + return "Exploring", newX, newY, nil // Fall back if fight creation fails + } + + user.FightID = fight.ID + return "Fighting", newX, newY, nil + } else { + fmt.Println("No fight...") + } + return "Exploring", newX, newY, nil } + +// getRandomMonsterByDistance selects a random monster based on distance from origin +func getRandomMonsterByDistance(x, y int) (*monsters.Monster, error) { + // Calculate Manhattan distance from origin + distance := int(math.Abs(float64(x)) + math.Abs(float64(y))) + + // Determine level range based on distance (every 5 units increases level) + minLevel := 1 + (distance / 5) + maxLevel := minLevel + 2 // 3-level range + + // Cap the levels at reasonable bounds + if minLevel > 50 { + minLevel = 50 + } + if maxLevel > 50 { + maxLevel = 50 + } + if minLevel < 1 { + minLevel = 1 + } + + // Get monsters in the level range + availableMonsters, err := monsters.ByLevelRange(minLevel, maxLevel) + if err != nil || len(availableMonsters) == 0 { + // Fallback to level 1 monsters if no monsters found + availableMonsters, err = monsters.ByLevel(1) + if err != nil || len(availableMonsters) == 0 { + return nil, fmt.Errorf("no monsters available") + } + } + + // Pick a random monster from available ones + randomIndex := rand.Intn(len(availableMonsters)) + return availableMonsters[randomIndex], nil +} diff --git a/internal/middleware/fights.go b/internal/middleware/fights.go new file mode 100644 index 0000000..be4db95 --- /dev/null +++ b/internal/middleware/fights.go @@ -0,0 +1,59 @@ +package middleware + +import ( + "dk/internal/models/users" + "dk/internal/router" + "strings" + + "github.com/valyala/fasthttp" +) + +// RequireFighting ensures the user is in a fight when accessing fight routes +func RequireFighting() router.Middleware { + return func(next router.Handler) router.Handler { + return func(ctx router.Ctx, params []string) { + user, ok := ctx.UserValue("user").(*users.User) + if !ok || user == nil { + ctx.SetStatusCode(fasthttp.StatusUnauthorized) + ctx.SetBodyString("Not authenticated") + return + } + + if !user.IsFighting() { + ctx.Redirect("/", 303) + return + } + + next(ctx, params) + } + } +} + +// HandleFightRedirect redirects users to appropriate locations based on fight status +func HandleFightRedirect() router.Middleware { + return func(next router.Handler) router.Handler { + return func(ctx router.Ctx, params []string) { + user, ok := ctx.UserValue("user").(*users.User) + if !ok || user == nil { + next(ctx, params) + return + } + + currentPath := string(ctx.URI().Path()) + + // If user is fighting and not on fight page, redirect to fight + if user.IsFighting() && !strings.HasPrefix(currentPath, "/fight") { + ctx.Redirect("/fight", 303) + return + } + + // If user is not fighting and on fight page, redirect to home + if !user.IsFighting() && strings.HasPrefix(currentPath, "/fight") { + ctx.Redirect("/", 303) + return + } + + next(ctx, params) + } + } +} diff --git a/internal/models/fights/fights.go b/internal/models/fights/fights.go index 9aa46db..67a7284 100644 --- a/internal/models/fights/fights.go +++ b/internal/models/fights/fights.go @@ -51,7 +51,7 @@ func New(userID, monsterID int) *Fight { UberDamage: 0, UberDefense: 0, FirstStrike: false, - Turn: 1, + Turn: 0, RanAway: false, Victory: false, Won: false, @@ -71,8 +71,8 @@ func (f *Fight) Validate() error { if f.MonsterID <= 0 { return fmt.Errorf("fight MonsterID must be positive") } - if f.Turn < 1 { - return fmt.Errorf("fight Turn must be at least 1") + if f.Turn < 0 { + return fmt.Errorf("fight Turn cannot be negative") } if f.MonsterHP < 0 { return fmt.Errorf("fight MonsterHP cannot be negative") diff --git a/internal/routes/auth.go b/internal/routes/auth.go index 24f0535..f15fa12 100644 --- a/internal/routes/auth.go +++ b/internal/routes/auth.go @@ -33,15 +33,8 @@ func RegisterAuthRoutes(r *router.Router) { // showLogin displays the login form func showLogin(ctx router.Ctx, _ []string) { sess := ctx.UserValue("session").(*session.Session) - var errorHTML string var id string - if flash, exists := sess.GetFlash("error"); exists { - if msg, ok := flash.(string); ok { - errorHTML = fmt.Sprintf(`
%s
`, msg) - } - } - if formData, exists := sess.Get("form_data"); exists { if data, ok := formData.(map[string]string); ok { id = data["id"] @@ -51,8 +44,7 @@ func showLogin(ctx router.Ctx, _ []string) { session.Store(sess) components.RenderPage(ctx, "Log In", "auth/login.html", map[string]any{ - "error_message": errorHTML, - "id": id, + "id": id, }) } diff --git a/internal/routes/fight.go b/internal/routes/fight.go new file mode 100644 index 0000000..2186824 --- /dev/null +++ b/internal/routes/fight.go @@ -0,0 +1,43 @@ +package routes + +import ( + "dk/internal/auth" + "dk/internal/components" + "dk/internal/middleware" + "dk/internal/models/fights" + "dk/internal/models/users" + "dk/internal/router" + "math/rand" +) + +func RegisterFightRoutes(r *router.Router) { + group := r.Group("/fight") + group.Use(auth.RequireAuth()) + group.Use(middleware.RequireFighting()) + + group.Get("/", showFight) +} + +func showFight(ctx router.Ctx, _ []string) { + user := ctx.UserValue("user").(*users.User) + + fight, err := fights.Find(user.FightID) + if err != nil { + ctx.SetContentType("text/plain") + ctx.SetBodyString("Fight not found") + return + } + + // If turn 0, determine first strike and advance to turn 1 + if fight.Turn == 0 { + // 50% chance user goes first + fight.FirstStrike = rand.Float32() < 0.5 + fight.Turn = 1 + fight.Save() + } + + components.RenderPage(ctx, "Fighting", "fight/fight.html", map[string]any{ + "fight": fight, + "user": user, + }) +} diff --git a/internal/routes/index.go b/internal/routes/index.go index b168df0..6c64a0a 100644 --- a/internal/routes/index.go +++ b/internal/routes/index.go @@ -23,13 +23,23 @@ func Index(ctx router.Ctx, _ []string) { ctx.Redirect("/town", 303) case "Exploring": ctx.Redirect("/explore", 303) + case "Fighting": + ctx.Redirect("/fight", 303) default: ctx.Redirect("/explore", 303) } } func Move(ctx router.Ctx, _ []string) { + sess := ctx.UserValue("session").(*session.Session) user := ctx.UserValue("user").(*users.User) + + if user.Currently == "Fighting" { + sess.SetFlash("error", "You can't just run from a fight!") + ctx.Redirect("/fight", 303) + return + } + dir, err := strconv.Atoi(string(ctx.PostArgs().Peek("direction"))) if err != nil { ctx.SetContentType("text/plain") @@ -46,13 +56,16 @@ func Move(ctx router.Ctx, _ []string) { user.Currently = currently user.SetPosition(newX, newY) + user.Save() - if currently == "In Town" { + switch currently { + case "In Town": ctx.Redirect("/town", 303) - return + case "Fighting": + ctx.Redirect("/fight", 303) + default: + ctx.Redirect("/explore", 303) } - - ctx.Redirect("/explore", 303) } func Explore(ctx router.Ctx, _ []string) { diff --git a/internal/routes/town.go b/internal/routes/town.go index 2edae54..f87478d 100644 --- a/internal/routes/town.go +++ b/internal/routes/town.go @@ -50,21 +50,10 @@ func showTown(ctx router.Ctx, _ []string) { } func showInn(ctx router.Ctx, _ []string) { - sess := ctx.UserValue("session").(*session.Session) - var errorHTML string - - if flash, exists := sess.GetFlash("error"); exists { - if msg, ok := flash.(string); ok { - errorHTML = `
` + msg + "
" - } - } - town := ctx.UserValue("town").(*towns.Town) - components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{ - "town": town, - "rested": false, - "error_message": errorHTML, + "town": town, + "rested": false, }) } diff --git a/main.go b/main.go index bdd6453..5b06c64 100644 --- a/main.go +++ b/main.go @@ -189,6 +189,7 @@ func start(port string) error { routes.RegisterAuthRoutes(r) routes.RegisterTownRoutes(r) + routes.RegisterFightRoutes(r) // Use current working directory for static files assetsDir := filepath.Join(cwd, "assets") diff --git a/templates/fight/fight.html b/templates/fight/fight.html new file mode 100644 index 0000000..1554fdc --- /dev/null +++ b/templates/fight/fight.html @@ -0,0 +1,5 @@ +{include "layout.html"} + +{block "content"} +ITS A FIGHT! +{/block} diff --git a/templates/leftside.html b/templates/leftside.html index 4058285..f8fd59b 100644 --- a/templates/leftside.html +++ b/templates/leftside.html @@ -12,15 +12,19 @@ View Map -
- {csrf} - -
- - -
- -
+ {if user.Currently != "Fighting"} +
+ {csrf} + +
+ + +
+ +
+ {else} + Can't run! + {/if}