package actions import ( "dk/internal/database" "dk/internal/helpers/exp" "dk/internal/models/fightlogs" "dk/internal/models/fights" "dk/internal/models/monsters" "dk/internal/models/spells" "dk/internal/models/towns" "dk/internal/models/users" "fmt" "math" "math/rand" ) type FightResult struct { FightUpdates map[string]any UserUpdates map[string]any LogAction func() error ActionText string // Add this field 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 newLevel, newStr, newDex, newExp := user.CalculateLevelUp(rewardExp) r.UserUpdates = map[string]any{ "fight_id": 0, "currently": "Exploring", "gold": user.Gold + rewardGold, "exp": newExp, "level": newLevel, "strength": newStr, "dexterity": newDex, } r.Ended = true r.Victory = true r.Won = true r.RewardGold = rewardGold r.RewardExp = rewardExp } func HandleAttack(fight *fights.Fight, user *users.User) *FightResult { monster, err := monsters.Find(fight.MonsterID) if err != nil { return &FightResult{ LogAction: func() error { return fightlogs.AddAction(fight.ID, "Monster not found!") }, ActionText: "Monster not found!", } } // Calculate damage attackPower := float64(user.Attack) minAttack := attackPower * 0.75 maxAttack := attackPower rawAttack := math.Ceil(rand.Float64()*(maxAttack-minAttack) + minAttack) tohit := rawAttack / (1.2 + math.Sqrt(attackPower)*0.05) damageMultiplier := 1 + math.Pow(float64(user.Attack)/400, 0.6) tohit *= damageMultiplier // Critical hit critChance := 85 * math.Pow(float64(user.Strength)/300, 0.6) criticalRoll := rand.Float64() * 100 if criticalRoll <= critChance { tohit *= 2 } // Monster defense armor := float64(monster.Armor) minBlock := armor * 0.75 maxBlock := armor rawBlock := math.Ceil(rand.Float64()*(maxBlock-minBlock) + minBlock) toblock := rawBlock / (1.8 + math.Sqrt(armor)*0.08) damage := tohit - toblock if damage < 1 { damage = 1 } if fight.UberDamage > 0 { bonus := math.Ceil(damage * float64(fight.UberDamage) / 100) damage += bonus } finalDamage := int(damage) newMonsterHP := max(fight.MonsterHP-finalDamage, 0) actionText := fmt.Sprintf("You attacked for %d damage!", finalDamage) result := &FightResult{ FightUpdates: map[string]any{"monster_hp": newMonsterHP}, LogAction: func() error { return fightlogs.AddAttackHit(fight.ID, finalDamage) }, ActionText: actionText, } // Check if monster defeated if newMonsterHP <= 0 { result.EndFightWithVictory(monster, user) result.ActionText = actionText + " " + fmt.Sprintf("%s has been defeated!", monster.Name) result.LogAction = func() error { if err := fightlogs.AddAttackHit(fight.ID, finalDamage); err != nil { return err } return fightlogs.AddMonsterDeath(fight.ID, monster.Name) } } return result } func HandleSpell(fight *fights.Fight, user *users.User, spellID int) *FightResult { spell, err := spells.Find(spellID) if err != nil { return &FightResult{ LogAction: func() error { return fightlogs.AddAction(fight.ID, "Spell not found!") }, ActionText: "Spell not found!", } } if user.MP < spell.MP { actionText := "Not enough MP to cast " + spell.Name + "!" return &FightResult{ LogAction: func() error { return fightlogs.AddAction(fight.ID, actionText) }, ActionText: actionText, } } if !user.HasSpell(spellID) { actionText := "You don't know that spell!" return &FightResult{ LogAction: func() error { return fightlogs.AddAction(fight.ID, actionText) }, ActionText: actionText, } } result := &FightResult{ UserUpdates: map[string]any{"mp": user.MP - spell.MP}, } switch spell.Type { case spells.TypeHealing: newHP := min(user.HP+spell.Attribute, user.MaxHP) result.UserUpdates["hp"] = newHP result.ActionText = fmt.Sprintf("You cast %s and healed %d HP!", spell.Name, spell.Attribute) result.LogAction = func() error { return fightlogs.AddSpellHeal(fight.ID, spell.Name, spell.Attribute) } case spells.TypeHurt: newMonsterHP := max(fight.MonsterHP-spell.Attribute, 0) result.FightUpdates = map[string]any{"monster_hp": newMonsterHP} result.ActionText = fmt.Sprintf("You cast %s and dealt %d damage!", spell.Name, spell.Attribute) result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Attribute) } if newMonsterHP <= 0 { monster, err := monsters.Find(fight.MonsterID) if err == nil { result.EndFightWithVictory(monster, user) result.ActionText += " " + fmt.Sprintf("%s has been defeated!", monster.Name) } } default: result.ActionText = "You cast " + spell.Name + " but nothing happened!" result.LogAction = func() error { return fightlogs.AddAction(fight.ID, result.ActionText) } } return result } func HandleRun(fight *fights.Fight, user *users.User) *FightResult { result := &FightResult{} if rand.Float32() < 0.2 { result.FightUpdates = map[string]any{"ran_away": true} result.UserUpdates = map[string]any{ "fight_id": 0, "currently": "Exploring", } result.ActionText = "You successfully ran away!" result.LogAction = func() error { return fightlogs.AddRunSuccess(fight.ID) } result.Ended = true } else { result.ActionText = "You failed to run away!" result.LogAction = func() error { return fightlogs.AddRunFail(fight.ID) } } return result } func HandleMonsterAttack(fight *fights.Fight, user *users.User) *FightResult { monster, err := monsters.Find(fight.MonsterID) if err != nil { return &FightResult{} } // Calculate damage attackPower := float64(monster.MaxDmg) minAttack := attackPower * 0.75 maxAttack := attackPower tohit := math.Ceil(rand.Float64()*(maxAttack-minAttack)+minAttack) / 3 defense := float64(user.Defense) minBlock := defense * 0.75 maxBlock := defense toblock := math.Ceil(rand.Float64()*(maxBlock-minBlock)+minBlock) / 3 damage := tohit - toblock if damage < 1 { damage = 1 } if fight.UberDefense > 0 { reduction := math.Ceil(damage * float64(fight.UberDefense) / 100) damage -= reduction if damage < 1 { damage = 1 } } finalDamage := int(damage) newHP := max(user.HP-finalDamage, 0) result := &FightResult{ UserUpdates: map[string]any{"hp": newHP}, ActionText: fmt.Sprintf("%s attacks for %d damage!", monster.Name, finalDamage), LogAction: func() error { return fightlogs.AddMonsterAttack(fight.ID, monster.Name, finalDamage) }, } if newHP <= 0 { closestTown := findClosestTown(user.X, user.Y) townX, townY := 0, 0 if closestTown != nil { townX, townY = closestTown.X, closestTown.Y } result.FightUpdates = map[string]any{ "victory": true, "won": false, } result.UserUpdates = map[string]any{ "fight_id": 0, "currently": "In Town", "hp": user.MaxHP / 4, "gold": (user.Gold * 3) / 4, "x": townX, "y": townY, } result.Ended = true result.Victory = true result.Won = false } return result } type LevelStats struct { Strength int Dexterity int } func calculateLevelUp(currentLevel, newExp, currentStr, currentDex int) (int, LevelStats) { level := currentLevel str := currentStr dex := currentDex nexp := newExp for { expNeeded := exp.Calc(level + 1) if nexp < expNeeded { break } level++ str++ dex++ nexp -= expNeeded } return level, LevelStats{Strength: str, Dexterity: dex} } func findClosestTown(x, y int) *towns.Town { allTowns, err := towns.All() if err != nil || len(allTowns) == 0 { return nil } var closest *towns.Town var minDistance float64 for _, town := range allTowns { distance := town.DistanceFromSquared(x, y) if closest == nil || distance < minDistance { closest = town minDistance = distance } } return closest } func calculateRewards(monster *monsters.Monster, user *users.User) (int, int) { // More generous reward range: 90-100% of max instead of 83-100% minExp := (monster.MaxExp * 9) / 10 maxExp := monster.MaxExp exp := max(rand.Intn(maxExp-minExp+1)+minExp, 1) minGold := (monster.MaxGold * 9) / 10 maxGold := monster.MaxGold gold := max(rand.Intn(maxGold-minGold+1)+minGold, 1) // Apply bonuses expBonus := (user.ExpBonus * exp) / 100 exp += expBonus goldBonus := (user.GoldBonus * gold) / 100 gold += goldBonus return gold, exp } func ExecuteFightAction(fightID int, result *FightResult) error { return database.Transaction(func() error { // Update fight if len(result.FightUpdates) > 0 { if err := database.Update("fights", result.FightUpdates, "id", fightID); err != nil { return err } } // Update user if len(result.UserUpdates) > 0 { fight, err := fights.Find(fightID) if err != nil { return err } if err := database.Update("users", result.UserUpdates, "id", fight.UserID); err != nil { return err } } // Add log entry if result.LogAction != nil { return result.LogAction() } return nil }) }