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(`