256 lines
6.0 KiB
Go
256 lines
6.0 KiB
Go
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
|
|
}
|