563 lines
15 KiB
Go
563 lines
15 KiB
Go
package items
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
// 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 {
|
|
return eil.CalculateEquipmentBonusesWithEntity(nil)
|
|
}
|
|
|
|
// CalculateEquipmentBonusesWithEntity calculates stat bonuses from all equipped items with entity modifiers
|
|
func (eil *EquipmentItemList) CalculateEquipmentBonusesWithEntity(entity Entity) *ItemStatsValues {
|
|
eil.mutex.RLock()
|
|
defer eil.mutex.RUnlock()
|
|
|
|
totalBonuses := &ItemStatsValues{}
|
|
|
|
// We need access to the master item list to calculate bonuses
|
|
// This would typically be injected or passed as a parameter
|
|
// For now, we'll just accumulate basic stats from the items
|
|
for _, item := range eil.items {
|
|
if item != nil {
|
|
// TODO: Implement item bonus calculation with master item list
|
|
// This should call mil.CalculateItemBonusesFromItem(item, entity)
|
|
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() {
|
|
// Equipment item list system initialized
|
|
}
|