229 lines
5.2 KiB
Go
229 lines
5.2 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
|
|
attackPower := float64(user.Attack)
|
|
minAttack := attackPower * 0.75
|
|
maxAttack := attackPower
|
|
tohit := math.Ceil(rand.Float64()*(maxAttack-minAttack)+minAttack) / 3
|
|
|
|
// 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
|
|
armor := float64(monster.Armor)
|
|
minBlock := armor * 0.75
|
|
maxBlock := armor
|
|
toblock := math.Ceil(rand.Float64()*(maxBlock-minBlock)+minBlock) / 3
|
|
|
|
// 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)
|
|
fight.WinFight(fight.RewardGold, fight.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.Exp += 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
|
|
}
|