eq2go/internal/items/equipment_list.go

556 lines
14 KiB
Go

package items
import (
"fmt"
"log"
)
// NewEquipmentItemList creates a new equipment item list
func NewEquipmentItemList() *EquipmentItemList {
return &EquipmentItemList{
items: [NumSlots]*Item{},
appearanceType: BaseEquipment,
}
}
// NewEquipmentItemListFromCopy creates a copy of an equipment list
func NewEquipmentItemListFromCopy(source *EquipmentItemList) *EquipmentItemList {
if source == nil {
return NewEquipmentItemList()
}
source.mutex.RLock()
defer source.mutex.RUnlock()
equipment := &EquipmentItemList{
appearanceType: source.appearanceType,
}
// Copy all equipped items
for i, item := range source.items {
if item != nil {
equipment.items[i] = item.Copy()
}
}
return equipment
}
// GetAllEquippedItems returns all equipped items
func (eil *EquipmentItemList) GetAllEquippedItems() []*Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
var equippedItems []*Item
for _, item := range eil.items {
if item != nil {
equippedItems = append(equippedItems, item)
}
}
return equippedItems
}
// ResetPackets resets packet data
func (eil *EquipmentItemList) ResetPackets() {
eil.mutex.Lock()
defer eil.mutex.Unlock()
eil.xorPacket = nil
eil.origPacket = nil
}
// HasItem checks if a specific item ID is equipped
func (eil *EquipmentItemList) HasItem(itemID int32) bool {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
for _, item := range eil.items {
if item != nil && item.Details.ItemID == itemID {
return true
}
}
return false
}
// GetNumberOfItems returns the number of equipped items
func (eil *EquipmentItemList) GetNumberOfItems() int8 {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
count := int8(0)
for _, item := range eil.items {
if item != nil {
count++
}
}
return count
}
// GetWeight returns the total weight of equipped items
func (eil *EquipmentItemList) GetWeight() int32 {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
totalWeight := int32(0)
for _, item := range eil.items {
if item != nil {
totalWeight += item.GenericInfo.Weight * int32(item.Details.Count)
}
}
return totalWeight
}
// GetItemFromUniqueID gets an equipped item by unique ID
func (eil *EquipmentItemList) GetItemFromUniqueID(uniqueID int32) *Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
for _, item := range eil.items {
if item != nil && int32(item.Details.UniqueID) == uniqueID {
return item
}
}
return nil
}
// GetItemFromItemID gets an equipped item by item template ID
func (eil *EquipmentItemList) GetItemFromItemID(itemID int32) *Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
for _, item := range eil.items {
if item != nil && item.Details.ItemID == itemID {
return item
}
}
return nil
}
// SetItem sets an item in a specific equipment slot
func (eil *EquipmentItemList) SetItem(slotID int8, item *Item, locked bool) {
if slotID < 0 || slotID >= NumSlots {
return
}
if !locked {
eil.mutex.Lock()
defer eil.mutex.Unlock()
}
eil.items[slotID] = item
if item != nil {
item.Details.SlotID = int16(slotID)
item.Details.AppearanceType = int16(eil.appearanceType)
}
}
// RemoveItem removes an item from a specific slot
func (eil *EquipmentItemList) RemoveItem(slot int8, deleteItem bool) {
if slot < 0 || slot >= NumSlots {
return
}
eil.mutex.Lock()
defer eil.mutex.Unlock()
item := eil.items[slot]
eil.items[slot] = nil
if deleteItem && item != nil {
item.NeedsDeletion = true
}
}
// GetItem gets an item from a specific slot
func (eil *EquipmentItemList) GetItem(slotID int8) *Item {
if slotID < 0 || slotID >= NumSlots {
return nil
}
eil.mutex.RLock()
defer eil.mutex.RUnlock()
return eil.items[slotID]
}
// AddItem adds an item to the equipment (finds appropriate slot)
func (eil *EquipmentItemList) AddItem(slot int8, item *Item) bool {
if item == nil {
return false
}
// Check if the specific slot is requested and valid
if slot >= 0 && slot < NumSlots {
eil.mutex.Lock()
defer eil.mutex.Unlock()
if eil.items[slot] == nil {
eil.items[slot] = item
item.Details.SlotID = int16(slot)
item.Details.AppearanceType = int16(eil.appearanceType)
return true
}
}
// Find a free slot that the item can be equipped in
freeSlot := eil.GetFreeSlot(item, slot, 0)
if freeSlot < NumSlots {
eil.SetItem(freeSlot, item, false)
return true
}
return false
}
// CheckEquipSlot checks if an item can be equipped in a specific slot
func (eil *EquipmentItemList) CheckEquipSlot(item *Item, slot int8) bool {
if item == nil || slot < 0 || slot >= NumSlots {
return false
}
// Check if item has the required slot data
return item.HasSlot(slot, -1)
}
// CanItemBeEquippedInSlot checks if an item can be equipped in a slot
func (eil *EquipmentItemList) CanItemBeEquippedInSlot(item *Item, slot int8) bool {
if item == nil || slot < 0 || slot >= NumSlots {
return false
}
// Check slot compatibility
if !eil.CheckEquipSlot(item, slot) {
return false
}
// Check if slot is already occupied
eil.mutex.RLock()
defer eil.mutex.RUnlock()
return eil.items[slot] == nil
}
// GetFreeSlot finds a free slot for an item
func (eil *EquipmentItemList) GetFreeSlot(item *Item, preferredSlot int8, version int16) int8 {
if item == nil {
return NumSlots // Invalid slot
}
eil.mutex.RLock()
defer eil.mutex.RUnlock()
// If preferred slot is specified and available, use it
if preferredSlot >= 0 && preferredSlot < NumSlots {
if eil.items[preferredSlot] == nil && item.HasSlot(preferredSlot, -1) {
return preferredSlot
}
}
// Search through all possible slots for this item
for slot := int8(0); slot < NumSlots; slot++ {
if eil.items[slot] == nil && item.HasSlot(slot, -1) {
return slot
}
}
return NumSlots // No free slot found
}
// CheckSlotConflict checks for slot conflicts (lore items, etc.)
func (eil *EquipmentItemList) CheckSlotConflict(item *Item, checkLoreOnly bool, loreStackCount *int16) int32 {
if item == nil {
return 0
}
eil.mutex.RLock()
defer eil.mutex.RUnlock()
// Check for lore conflicts
if item.CheckFlag(Lore) || item.CheckFlag(LoreEquip) {
stackCount := int16(0)
for _, equippedItem := range eil.items {
if equippedItem != nil && equippedItem.Details.ItemID == item.Details.ItemID {
stackCount++
}
}
if loreStackCount != nil {
*loreStackCount = stackCount
}
if stackCount > 0 {
return 1 // Lore conflict
}
}
return 0 // No conflict
}
// GetSlotByItem finds the slot an item is equipped in
func (eil *EquipmentItemList) GetSlotByItem(item *Item) int8 {
if item == nil {
return NumSlots
}
eil.mutex.RLock()
defer eil.mutex.RUnlock()
for slot, equippedItem := range eil.items {
if equippedItem == item {
return int8(slot)
}
}
return NumSlots // Not found
}
// CalculateEquipmentBonuses calculates stat bonuses from all equipped items
func (eil *EquipmentItemList) CalculateEquipmentBonuses() *ItemStatsValues {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
totalBonuses := &ItemStatsValues{}
for _, item := range eil.items {
if item != nil {
// TODO: Implement item bonus calculation
// This should be handled by the master item list
itemBonuses := &ItemStatsValues{} // placeholder
if itemBonuses != nil {
// Add item bonuses to total
totalBonuses.Str += itemBonuses.Str
totalBonuses.Sta += itemBonuses.Sta
totalBonuses.Agi += itemBonuses.Agi
totalBonuses.Wis += itemBonuses.Wis
totalBonuses.Int += itemBonuses.Int
totalBonuses.VsSlash += itemBonuses.VsSlash
totalBonuses.VsCrush += itemBonuses.VsCrush
totalBonuses.VsPierce += itemBonuses.VsPierce
totalBonuses.VsPhysical += itemBonuses.VsPhysical
totalBonuses.VsHeat += itemBonuses.VsHeat
totalBonuses.VsCold += itemBonuses.VsCold
totalBonuses.VsMagic += itemBonuses.VsMagic
totalBonuses.VsMental += itemBonuses.VsMental
totalBonuses.VsDivine += itemBonuses.VsDivine
totalBonuses.VsDisease += itemBonuses.VsDisease
totalBonuses.VsPoison += itemBonuses.VsPoison
totalBonuses.Health += itemBonuses.Health
totalBonuses.Power += itemBonuses.Power
totalBonuses.Concentration += itemBonuses.Concentration
totalBonuses.AbilityModifier += itemBonuses.AbilityModifier
totalBonuses.CriticalMitigation += itemBonuses.CriticalMitigation
totalBonuses.ExtraShieldBlockChance += itemBonuses.ExtraShieldBlockChance
totalBonuses.BeneficialCritChance += itemBonuses.BeneficialCritChance
totalBonuses.CritBonus += itemBonuses.CritBonus
totalBonuses.Potency += itemBonuses.Potency
totalBonuses.HateGainMod += itemBonuses.HateGainMod
totalBonuses.AbilityReuseSpeed += itemBonuses.AbilityReuseSpeed
totalBonuses.AbilityCastingSpeed += itemBonuses.AbilityCastingSpeed
totalBonuses.AbilityRecoverySpeed += itemBonuses.AbilityRecoverySpeed
totalBonuses.SpellReuseSpeed += itemBonuses.SpellReuseSpeed
totalBonuses.SpellMultiAttackChance += itemBonuses.SpellMultiAttackChance
totalBonuses.DPS += itemBonuses.DPS
totalBonuses.AttackSpeed += itemBonuses.AttackSpeed
totalBonuses.MultiAttackChance += itemBonuses.MultiAttackChance
totalBonuses.Flurry += itemBonuses.Flurry
totalBonuses.AEAutoattackChance += itemBonuses.AEAutoattackChance
totalBonuses.Strikethrough += itemBonuses.Strikethrough
totalBonuses.Accuracy += itemBonuses.Accuracy
totalBonuses.OffensiveSpeed += itemBonuses.OffensiveSpeed
totalBonuses.UncontestedParry += itemBonuses.UncontestedParry
totalBonuses.UncontestedBlock += itemBonuses.UncontestedBlock
totalBonuses.UncontestedDodge += itemBonuses.UncontestedDodge
totalBonuses.UncontestedRiposte += itemBonuses.UncontestedRiposte
totalBonuses.SizeMod += itemBonuses.SizeMod
}
}
}
return totalBonuses
}
// SetAppearanceType sets the appearance type (normal or appearance equipment)
func (eil *EquipmentItemList) SetAppearanceType(appearanceType int8) {
eil.mutex.Lock()
defer eil.mutex.Unlock()
eil.appearanceType = appearanceType
// Update all equipped items with new appearance type
for _, item := range eil.items {
if item != nil {
item.Details.AppearanceType = int16(appearanceType)
}
}
}
// GetAppearanceType gets the current appearance type
func (eil *EquipmentItemList) GetAppearanceType() int8 {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
return eil.appearanceType
}
// ValidateEquipment validates all equipped items
func (eil *EquipmentItemList) ValidateEquipment() *ItemValidationResult {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
result := &ItemValidationResult{Valid: true}
for slot, item := range eil.items {
if item != nil {
// Validate item
itemResult := item.Validate()
if !itemResult.Valid {
result.Valid = false
for _, err := range itemResult.Errors {
result.Errors = append(result.Errors, fmt.Sprintf("Slot %d: %s", slot, err))
}
}
// Check slot compatibility
if !item.HasSlot(int8(slot), -1) {
result.Valid = false
result.Errors = append(result.Errors, fmt.Sprintf("Item %s cannot be equipped in slot %d", item.Name, slot))
}
}
}
return result
}
// GetEquippedItemsByType returns equipped items of a specific type
func (eil *EquipmentItemList) GetEquippedItemsByType(itemType int8) []*Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
var matchingItems []*Item
for _, item := range eil.items {
if item != nil && item.GenericInfo.ItemType == itemType {
matchingItems = append(matchingItems, item)
}
}
return matchingItems
}
// GetWeapons returns all equipped weapons
func (eil *EquipmentItemList) GetWeapons() []*Item {
return eil.GetEquippedItemsByType(ItemTypeWeapon)
}
// GetArmor returns all equipped armor pieces
func (eil *EquipmentItemList) GetArmor() []*Item {
return eil.GetEquippedItemsByType(ItemTypeArmor)
}
// GetJewelry returns all equipped jewelry
func (eil *EquipmentItemList) GetJewelry() []*Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
var jewelry []*Item
// Check ring slots
if eil.items[EQ2LRingSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2LRingSlot])
}
if eil.items[EQ2RRingSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2RRingSlot])
}
// Check ear slots
if eil.items[EQ2EarsSlot1] != nil {
jewelry = append(jewelry, eil.items[EQ2EarsSlot1])
}
if eil.items[EQ2EarsSlot2] != nil {
jewelry = append(jewelry, eil.items[EQ2EarsSlot2])
}
// Check neck slot
if eil.items[EQ2NeckSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2NeckSlot])
}
// Check wrist slots
if eil.items[EQ2LWristSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2LWristSlot])
}
if eil.items[EQ2RWristSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2RWristSlot])
}
return jewelry
}
// HasWeaponEquipped checks if any weapon is equipped
func (eil *EquipmentItemList) HasWeaponEquipped() bool {
weapons := eil.GetWeapons()
return len(weapons) > 0
}
// HasShieldEquipped checks if a shield is equipped
func (eil *EquipmentItemList) HasShieldEquipped() bool {
item := eil.GetItem(EQ2SecondarySlot)
return item != nil && item.IsShield()
}
// HasTwoHandedWeapon checks if a two-handed weapon is equipped
func (eil *EquipmentItemList) HasTwoHandedWeapon() bool {
primaryItem := eil.GetItem(EQ2PrimarySlot)
if primaryItem != nil && primaryItem.IsWeapon() && primaryItem.WeaponInfo != nil {
return primaryItem.WeaponInfo.WieldType == ItemWieldTypeTwoHand
}
return false
}
// CanDualWield checks if dual wielding is possible with current equipment
func (eil *EquipmentItemList) CanDualWield() bool {
primaryItem := eil.GetItem(EQ2PrimarySlot)
secondaryItem := eil.GetItem(EQ2SecondarySlot)
if primaryItem != nil && secondaryItem != nil {
// Both items must be weapons that can be dual wielded
if primaryItem.IsWeapon() && secondaryItem.IsWeapon() {
if primaryItem.WeaponInfo != nil && secondaryItem.WeaponInfo != nil {
return primaryItem.WeaponInfo.WieldType == ItemWieldTypeDual &&
secondaryItem.WeaponInfo.WieldType == ItemWieldTypeDual
}
}
}
return false
}
// String returns a string representation of the equipment list
func (eil *EquipmentItemList) String() string {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
equippedCount := 0
for _, item := range eil.items {
if item != nil {
equippedCount++
}
}
return fmt.Sprintf("EquipmentItemList{Equipped: %d/%d, AppearanceType: %d}",
equippedCount, NumSlots, eil.appearanceType)
}
func init() {
log.Printf("Equipment item list system initialized")
}