360 lines
9.6 KiB
Go
360 lines
9.6 KiB
Go
package actions
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"dk/internal/models/items"
|
|
"dk/internal/models/users"
|
|
)
|
|
|
|
// UserEquipItem calculates equipment updates for a user equipping an item.
|
|
// Returns map of database field updates without modifying the user struct.
|
|
func UserEquipItem(user *users.User, item *items.Item) (map[string]any, error) {
|
|
if !item.IsEquippable() && !item.IsSlottable() {
|
|
return nil, fmt.Errorf("item %s is not equippable", item.Name)
|
|
}
|
|
|
|
updates := make(map[string]any)
|
|
|
|
// Calculate stat changes by removing old item and adding new item
|
|
newStats := calculateNewStats(user, item)
|
|
|
|
// Update equipment slots and stats based on item type
|
|
switch item.Type {
|
|
case items.TypeWeapon:
|
|
updates["weapon_id"] = item.ID
|
|
updates["weapon_name"] = item.Name
|
|
|
|
case items.TypeArmor:
|
|
updates["armor_id"] = item.ID
|
|
updates["armor_name"] = item.Name
|
|
|
|
case items.TypeShield:
|
|
updates["shield_id"] = item.ID
|
|
updates["shield_name"] = item.Name
|
|
|
|
case items.TypeAccessory:
|
|
// Find first available slot or replace slot 1
|
|
if user.Slot1ID == 0 {
|
|
updates["slot_1_id"] = item.ID
|
|
updates["slot_1_name"] = item.Name
|
|
} else if user.Slot2ID == 0 {
|
|
updates["slot_2_id"] = item.ID
|
|
updates["slot_2_name"] = item.Name
|
|
} else if user.Slot3ID == 0 {
|
|
updates["slot_3_id"] = item.ID
|
|
updates["slot_3_name"] = item.Name
|
|
} else {
|
|
// All slots full, replace slot 1
|
|
updates["slot_1_id"] = item.ID
|
|
updates["slot_1_name"] = item.Name
|
|
}
|
|
}
|
|
|
|
// Apply stat changes
|
|
updates["attack"] = newStats.Attack
|
|
updates["defense"] = newStats.Defense
|
|
updates["strength"] = newStats.Strength
|
|
updates["dexterity"] = newStats.Dexterity
|
|
updates["max_hp"] = newStats.MaxHP
|
|
updates["max_mp"] = newStats.MaxMP
|
|
updates["exp_bonus"] = newStats.ExpBonus
|
|
updates["gold_bonus"] = newStats.GoldBonus
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
// UserEquipAccessoryToSlot equips an accessory to a specific slot (1-3)
|
|
func UserEquipAccessoryToSlot(user *users.User, item *items.Item, slot int) (map[string]any, error) {
|
|
if !item.IsSlottable() {
|
|
return nil, fmt.Errorf("item %s is not slottable", item.Name)
|
|
}
|
|
|
|
if slot < 1 || slot > 3 {
|
|
return nil, fmt.Errorf("invalid slot number: %d", slot)
|
|
}
|
|
|
|
updates := make(map[string]any)
|
|
newStats := calculateNewStatsForSlot(user, item, slot)
|
|
|
|
// Update the specific slot
|
|
switch slot {
|
|
case 1:
|
|
updates["slot_1_id"] = item.ID
|
|
updates["slot_1_name"] = item.Name
|
|
case 2:
|
|
updates["slot_2_id"] = item.ID
|
|
updates["slot_2_name"] = item.Name
|
|
case 3:
|
|
updates["slot_3_id"] = item.ID
|
|
updates["slot_3_name"] = item.Name
|
|
}
|
|
|
|
// Apply stat changes
|
|
updates["attack"] = newStats.Attack
|
|
updates["defense"] = newStats.Defense
|
|
updates["strength"] = newStats.Strength
|
|
updates["dexterity"] = newStats.Dexterity
|
|
updates["max_hp"] = newStats.MaxHP
|
|
updates["max_mp"] = newStats.MaxMP
|
|
updates["exp_bonus"] = newStats.ExpBonus
|
|
updates["gold_bonus"] = newStats.GoldBonus
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
// UserUnequipItem removes an equipped item and recalculates stats
|
|
func UserUnequipItem(user *users.User, itemType int) (map[string]any, error) {
|
|
updates := make(map[string]any)
|
|
|
|
switch itemType {
|
|
case items.TypeWeapon:
|
|
if user.WeaponID == 0 {
|
|
return nil, fmt.Errorf("no weapon equipped")
|
|
}
|
|
updates["weapon_id"] = 0
|
|
updates["weapon_name"] = ""
|
|
|
|
case items.TypeArmor:
|
|
if user.ArmorID == 0 {
|
|
return nil, fmt.Errorf("no armor equipped")
|
|
}
|
|
updates["armor_id"] = 0
|
|
updates["armor_name"] = ""
|
|
|
|
case items.TypeShield:
|
|
if user.ShieldID == 0 {
|
|
return nil, fmt.Errorf("no shield equipped")
|
|
}
|
|
updates["shield_id"] = 0
|
|
updates["shield_name"] = ""
|
|
|
|
default:
|
|
return nil, fmt.Errorf("invalid item type for unequip: %d", itemType)
|
|
}
|
|
|
|
// Recalculate all stats after unequipping
|
|
newStats := recalculateAllStats(user, itemType, 0)
|
|
updates["attack"] = newStats.Attack
|
|
updates["defense"] = newStats.Defense
|
|
updates["strength"] = newStats.Strength
|
|
updates["dexterity"] = newStats.Dexterity
|
|
updates["max_hp"] = newStats.MaxHP
|
|
updates["max_mp"] = newStats.MaxMP
|
|
updates["exp_bonus"] = newStats.ExpBonus
|
|
updates["gold_bonus"] = newStats.GoldBonus
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
// UserUnequipAccessory removes an accessory from a specific slot
|
|
func UserUnequipAccessory(user *users.User, slot int) (map[string]any, error) {
|
|
if slot < 1 || slot > 3 {
|
|
return nil, fmt.Errorf("invalid slot number: %d", slot)
|
|
}
|
|
|
|
updates := make(map[string]any)
|
|
|
|
// Check if slot has an item
|
|
switch slot {
|
|
case 1:
|
|
if user.Slot1ID == 0 {
|
|
return nil, fmt.Errorf("slot 1 is empty")
|
|
}
|
|
updates["slot_1_id"] = 0
|
|
updates["slot_1_name"] = ""
|
|
case 2:
|
|
if user.Slot2ID == 0 {
|
|
return nil, fmt.Errorf("slot 2 is empty")
|
|
}
|
|
updates["slot_2_id"] = 0
|
|
updates["slot_2_name"] = ""
|
|
case 3:
|
|
if user.Slot3ID == 0 {
|
|
return nil, fmt.Errorf("slot 3 is empty")
|
|
}
|
|
updates["slot_3_id"] = 0
|
|
updates["slot_3_name"] = ""
|
|
}
|
|
|
|
// Recalculate stats after removing accessory
|
|
newStats := recalculateAllStats(user, items.TypeAccessory, slot)
|
|
updates["attack"] = newStats.Attack
|
|
updates["defense"] = newStats.Defense
|
|
updates["strength"] = newStats.Strength
|
|
updates["dexterity"] = newStats.Dexterity
|
|
updates["max_hp"] = newStats.MaxHP
|
|
updates["max_mp"] = newStats.MaxMP
|
|
updates["exp_bonus"] = newStats.ExpBonus
|
|
updates["gold_bonus"] = newStats.GoldBonus
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
// Stats represents calculated user stats
|
|
type Stats struct {
|
|
Attack int
|
|
Defense int
|
|
Strength int
|
|
Dexterity int
|
|
MaxHP int
|
|
MaxMP int
|
|
ExpBonus int
|
|
GoldBonus int
|
|
}
|
|
|
|
// calculateNewStats calculates new stats when equipping an item
|
|
func calculateNewStats(user *users.User, newItem *items.Item) Stats {
|
|
stats := Stats{
|
|
Attack: user.Attack,
|
|
Defense: user.Defense,
|
|
Strength: user.Strength,
|
|
Dexterity: user.Dexterity,
|
|
MaxHP: user.MaxHP,
|
|
MaxMP: user.MaxMP,
|
|
ExpBonus: user.ExpBonus,
|
|
GoldBonus: user.GoldBonus,
|
|
}
|
|
|
|
// Remove old item stats if slot occupied
|
|
switch newItem.Type {
|
|
case items.TypeWeapon:
|
|
if user.WeaponID != 0 {
|
|
if oldItem, err := items.Find(user.WeaponID); err == nil {
|
|
removeItemStats(&stats, oldItem)
|
|
}
|
|
}
|
|
case items.TypeArmor:
|
|
if user.ArmorID != 0 {
|
|
if oldItem, err := items.Find(user.ArmorID); err == nil {
|
|
removeItemStats(&stats, oldItem)
|
|
}
|
|
}
|
|
case items.TypeShield:
|
|
if user.ShieldID != 0 {
|
|
if oldItem, err := items.Find(user.ShieldID); err == nil {
|
|
removeItemStats(&stats, oldItem)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add new item stats
|
|
addItemStats(&stats, newItem)
|
|
return stats
|
|
}
|
|
|
|
// calculateNewStatsForSlot calculates stats when equipping to a specific accessory slot
|
|
func calculateNewStatsForSlot(user *users.User, newItem *items.Item, slot int) Stats {
|
|
stats := Stats{
|
|
Attack: user.Attack,
|
|
Defense: user.Defense,
|
|
Strength: user.Strength,
|
|
Dexterity: user.Dexterity,
|
|
MaxHP: user.MaxHP,
|
|
MaxMP: user.MaxMP,
|
|
ExpBonus: user.ExpBonus,
|
|
GoldBonus: user.GoldBonus,
|
|
}
|
|
|
|
// Remove old accessory stats from the specified slot
|
|
var oldItemID int
|
|
switch slot {
|
|
case 1:
|
|
oldItemID = user.Slot1ID
|
|
case 2:
|
|
oldItemID = user.Slot2ID
|
|
case 3:
|
|
oldItemID = user.Slot3ID
|
|
}
|
|
|
|
if oldItemID != 0 {
|
|
if oldItem, err := items.Find(oldItemID); err == nil {
|
|
removeItemStats(&stats, oldItem)
|
|
}
|
|
}
|
|
|
|
// Add new item stats
|
|
addItemStats(&stats, newItem)
|
|
return stats
|
|
}
|
|
|
|
// recalculateAllStats recalculates all stats from scratch, excluding specified item
|
|
func recalculateAllStats(user *users.User, excludeType, excludeSlot int) Stats {
|
|
// Start with base stats (these would come from class/level progression)
|
|
// For now, using current stats minus all equipment bonuses
|
|
stats := Stats{
|
|
Attack: 0, // Base attack from strength/level
|
|
Defense: 0, // Base defense from dexterity/level
|
|
Strength: user.Strength,
|
|
Dexterity: user.Dexterity,
|
|
MaxHP: user.MaxHP,
|
|
MaxMP: user.MaxMP,
|
|
ExpBonus: 0, // Base exp bonus
|
|
GoldBonus: 0, // Base gold bonus
|
|
}
|
|
|
|
// Add weapon stats (unless being removed)
|
|
if user.WeaponID != 0 && excludeType != items.TypeWeapon {
|
|
if item, err := items.Find(user.WeaponID); err == nil {
|
|
addItemStats(&stats, item)
|
|
}
|
|
}
|
|
|
|
// Add armor stats (unless being removed)
|
|
if user.ArmorID != 0 && excludeType != items.TypeArmor {
|
|
if item, err := items.Find(user.ArmorID); err == nil {
|
|
addItemStats(&stats, item)
|
|
}
|
|
}
|
|
|
|
// Add shield stats (unless being removed)
|
|
if user.ShieldID != 0 && excludeType != items.TypeShield {
|
|
if item, err := items.Find(user.ShieldID); err == nil {
|
|
addItemStats(&stats, item)
|
|
}
|
|
}
|
|
|
|
// Add accessory stats (unless specific slot being removed)
|
|
if user.Slot1ID != 0 && !(excludeType == items.TypeAccessory && excludeSlot == 1) {
|
|
if item, err := items.Find(user.Slot1ID); err == nil {
|
|
addItemStats(&stats, item)
|
|
}
|
|
}
|
|
if user.Slot2ID != 0 && !(excludeType == items.TypeAccessory && excludeSlot == 2) {
|
|
if item, err := items.Find(user.Slot2ID); err == nil {
|
|
addItemStats(&stats, item)
|
|
}
|
|
}
|
|
if user.Slot3ID != 0 && !(excludeType == items.TypeAccessory && excludeSlot == 3) {
|
|
if item, err := items.Find(user.Slot3ID); err == nil {
|
|
addItemStats(&stats, item)
|
|
}
|
|
}
|
|
|
|
return stats
|
|
}
|
|
|
|
// addItemStats adds an item's stats to the current stats
|
|
func addItemStats(stats *Stats, item *items.Item) {
|
|
stats.Attack += item.Attack
|
|
stats.Defense += item.Defense
|
|
stats.Strength += item.Strength
|
|
stats.Dexterity += item.Dexterity
|
|
stats.MaxHP += item.MaxHP
|
|
stats.MaxMP += item.MaxMP
|
|
stats.ExpBonus += item.ExpBonus
|
|
stats.GoldBonus += item.GoldBonus
|
|
}
|
|
|
|
// removeItemStats removes an item's stats from the current stats
|
|
func removeItemStats(stats *Stats, item *items.Item) {
|
|
stats.Attack -= item.Attack
|
|
stats.Defense -= item.Defense
|
|
stats.Strength -= item.Strength
|
|
stats.Dexterity -= item.Dexterity
|
|
stats.MaxHP -= item.MaxHP
|
|
stats.MaxMP -= item.MaxMP
|
|
stats.ExpBonus -= item.ExpBonus
|
|
stats.GoldBonus -= item.GoldBonus
|
|
}
|