package actions import ( "dk/internal/models/fights" "dk/internal/models/monsters" "dk/internal/models/spells" "dk/internal/models/towns" "dk/internal/models/users" "math" "math/rand" "strconv" ) func HandleAttack(fight *fights.Fight, user *users.User) { // Load monster data to get armor monster, err := monsters.Find(fight.MonsterID) if err != nil { fight.AddAction("Monster not found!") return } // Player attack damage calculation with sqrt scaling attackPower := float64(user.Attack) minAttack := attackPower * 0.75 maxAttack := attackPower rawAttack := math.Ceil(rand.Float64()*(maxAttack-minAttack) + minAttack) // Progressive scaling using square root for smooth progression tohit := rawAttack / (1.2 + math.Sqrt(attackPower)*0.05) // Critical hit chance based on strength criticalRoll := rand.Intn(150) + 1 if float64(criticalRoll) <= math.Sqrt(float64(user.Strength)) { tohit *= 2 // Critical hit } // Monster defense calculation with more aggressive scaling armor := float64(monster.Armor) minBlock := armor * 0.75 maxBlock := armor rawBlock := math.Ceil(rand.Float64()*(maxBlock-minBlock) + minBlock) // Armor uses higher divisor to balance against player attack toblock := rawBlock / (1.8 + math.Sqrt(armor)*0.08) // Calculate final damage damage := tohit - toblock if damage < 1 { damage = 1 // Minimum damage } // Apply uber damage bonus if fight.UberDamage > 0 { bonus := math.Ceil(damage * float64(fight.UberDamage) / 100) damage += bonus } finalDamage := int(damage) // Apply damage and add action fight.DamageMonster(finalDamage) fight.AddActionAttackHit(finalDamage) // Check if monster is defeated if fight.MonsterHP <= 0 { fight.AddActionMonsterDeath(monster.Name) rewardGold, rewardExp := calculateRewards(monster, user) fight.WinFight(rewardGold, rewardExp) HandleFightWin(fight, user) } } func HandleSpell(fight *fights.Fight, user *users.User, spellID int) { spell, err := spells.Find(spellID) if err != nil { fight.AddAction("Spell not found!") return } // Check if user has enough MP if user.MP < spell.MP { fight.AddAction("Not enough MP to cast " + spell.Name + "!") return } // Check if user knows this spell if !user.HasSpell(spellID) { fight.AddAction("You don't know that spell!") return } // Deduct MP user.MP -= spell.MP switch spell.Type { case spells.TypeHealing: // Heal user healAmount := spell.Attribute user.HP += healAmount if user.HP > user.MaxHP { user.HP = user.MaxHP } fight.AddAction("You cast " + spell.Name + " and healed " + strconv.Itoa(healAmount) + " HP!") case spells.TypeHurt: // Damage monster damage := spell.Attribute fight.DamageMonster(damage) fight.AddAction("You cast " + spell.Name + " and dealt " + strconv.Itoa(damage) + " damage!") // Check if monster is defeated if fight.MonsterHP <= 0 { fight.WinFight(10, 5) // Basic rewards } default: fight.AddAction("You cast " + spell.Name + " but nothing happened!") } } func HandleRun(fight *fights.Fight, user *users.User) { // 20% chance to successfully run away if rand.Float32() < 0.2 { fight.RunAway() user.FightID = 0 fight.AddAction("You successfully ran away!") } else { fight.AddAction("You failed to run away!") } } func HandleMonsterAttack(fight *fights.Fight, user *users.User) { // Load monster data monster, err := monsters.Find(fight.MonsterID) if err != nil { return } // Monster attack damage calculation attackPower := float64(monster.MaxDmg) minAttack := attackPower * 0.75 maxAttack := attackPower tohit := math.Ceil(rand.Float64()*(maxAttack-minAttack)+minAttack) / 3 // User defense calculation defense := float64(user.Defense) minBlock := defense * 0.75 maxBlock := defense toblock := math.Ceil(rand.Float64()*(maxBlock-minBlock)+minBlock) / 3 // Calculate final damage damage := tohit - toblock if damage < 1 { damage = 1 // Minimum damage } // Apply uber defense bonus (reduces damage taken) if fight.UberDefense > 0 { reduction := math.Ceil(damage * float64(fight.UberDefense) / 100) damage -= reduction if damage < 1 { damage = 1 // Still minimum 1 damage } } finalDamage := int(damage) // Apply damage to user user.HP -= finalDamage if user.HP < 0 { user.HP = 0 } // Add monster attack action using memory-optimized format fight.AddActionMonsterAttack(monster.Name, finalDamage) // Check if user is defeated if user.HP <= 0 { fight.LoseFight() HandleFightLoss(fight, user) } } func HandleFightWin(fight *fights.Fight, user *users.User) { // Add rewards to user user.GrantExp(fight.RewardExp) user.Gold += fight.RewardGold // Reset fight state user.FightID = 0 user.Currently = "Exploring" fight.Save() user.Save() } func HandleFightLoss(fight *fights.Fight, user *users.User) { // Find closest town to user's position closestTown := findClosestTown(user.X, user.Y) if closestTown != nil { user.X = closestTown.X user.Y = closestTown.Y } // Apply death penalties user.HP = user.MaxHP / 4 // 25% of max health user.Gold = (user.Gold * 3) / 4 // 75% of gold // Reset fight state user.FightID = 0 user.Currently = "In Town" fight.Save() user.Save() } 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) { // Base rewards (83-100% of max) minExp := (monster.MaxExp * 5) / 6 maxExp := monster.MaxExp exp := rand.Intn(maxExp-minExp+1) + minExp minGold := (monster.MaxGold * 5) / 6 maxGold := monster.MaxGold gold := rand.Intn(maxGold-minGold+1) + minGold // Apply bonus multipliers expBonus := (user.ExpBonus * exp) / 100 exp += expBonus goldBonus := (user.GoldBonus * gold) / 100 gold += goldBonus return gold, exp }