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" "math" "math/rand" ) type FightResult struct { FightUpdates map[string]any UserUpdates map[string]any LogAction func() error Ended bool Victory bool Won bool } 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!") }, } } // 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) // Critical hit criticalRoll := rand.Intn(150) + 1 if float64(criticalRoll) <= math.Sqrt(float64(user.Strength)) { 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 := fight.MonsterHP - finalDamage if newMonsterHP < 0 { newMonsterHP = 0 } result := &FightResult{ FightUpdates: map[string]any{"monster_hp": newMonsterHP}, LogAction: func() error { return fightlogs.AddAttackHit(fight.ID, finalDamage) }, } // Check if monster defeated if newMonsterHP <= 0 { rewardGold, rewardExp := calculateRewards(monster, user) result.FightUpdates["victory"] = true result.FightUpdates["won"] = true result.FightUpdates["reward_gold"] = rewardGold result.FightUpdates["reward_exp"] = rewardExp result.UserUpdates = map[string]any{ "fight_id": 0, "currently": "Exploring", "gold": user.Gold + rewardGold, "exp": user.Exp + rewardExp, } // Handle level up newLevel, newStats := calculateLevelUp(user.Level, user.Exp+rewardExp, user.Strength, user.Dexterity) if newLevel > user.Level { result.UserUpdates["level"] = newLevel result.UserUpdates["strength"] = newStats.Strength result.UserUpdates["dexterity"] = newStats.Dexterity } result.LogAction = func() error { if err := fightlogs.AddAttackHit(fight.ID, finalDamage); err != nil { return err } return fightlogs.AddMonsterDeath(fight.ID, monster.Name) } result.Ended = true result.Victory = true result.Won = true } 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!") }, } } if user.MP < spell.MP { return &FightResult{ LogAction: func() error { return fightlogs.AddAction(fight.ID, "Not enough MP to cast "+spell.Name+"!") }, } } if !user.HasSpell(spellID) { return &FightResult{ LogAction: func() error { return fightlogs.AddAction(fight.ID, "You don't know that spell!") }, } } result := &FightResult{ UserUpdates: map[string]any{"mp": user.MP - spell.MP}, } switch spell.Type { case spells.TypeHealing: newHP := user.HP + spell.Attribute if newHP > user.MaxHP { newHP = user.MaxHP } result.UserUpdates["hp"] = newHP result.LogAction = func() error { return fightlogs.AddSpellHeal(fight.ID, spell.Name, spell.Attribute) } case spells.TypeHurt: newMonsterHP := fight.MonsterHP - spell.Attribute if newMonsterHP < 0 { newMonsterHP = 0 } result.FightUpdates = map[string]any{"monster_hp": newMonsterHP} result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Attribute) } if newMonsterHP <= 0 { result.FightUpdates["victory"] = true result.FightUpdates["won"] = true result.FightUpdates["reward_gold"] = 10 result.FightUpdates["reward_exp"] = 5 result.UserUpdates["fight_id"] = 0 result.UserUpdates["currently"] = "Exploring" result.Ended = true result.Victory = true result.Won = true } default: result.LogAction = func() error { return fightlogs.AddAction(fight.ID, "You cast "+spell.Name+" but nothing happened!") } } 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.LogAction = func() error { return fightlogs.AddRunSuccess(fight.ID) } result.Ended = true } else { 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 := user.HP - finalDamage if newHP < 0 { newHP = 0 } result := &FightResult{ UserUpdates: map[string]any{"hp": newHP}, 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) { 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 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 }) }