362 lines
9.1 KiB
Go

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
})
}