eq2go/internal/items/master_list.go

714 lines
20 KiB
Go

package items
import (
"fmt"
"log"
"strings"
"time"
)
// NewMasterItemList creates a new master item list
func NewMasterItemList() *MasterItemList {
mil := &MasterItemList{
items: make(map[int32]*Item),
mappedItemStatsStrings: make(map[string]int32),
mappedItemStatTypeIDs: make(map[int32]string),
brokerItemMap: make(map[*VersionRange]map[int64]int64),
}
// Initialize mapped item stats
mil.initializeMappedStats()
return mil
}
// initializeMappedStats initializes the mapped item stats
func (mil *MasterItemList) initializeMappedStats() {
// Add all the mapped item stats as in the C++ constructor
// Basic stats
mil.AddMappedItemStat(ItemStatStr, "strength")
mil.AddMappedItemStat(ItemStatSta, "stamina")
mil.AddMappedItemStat(ItemStatAgi, "agility")
mil.AddMappedItemStat(ItemStatWis, "wisdom")
mil.AddMappedItemStat(ItemStatInt, "intelligence")
mil.AddMappedItemStat(ItemStatAdorning, "adorning")
mil.AddMappedItemStat(ItemStatAggression, "aggression")
mil.AddMappedItemStat(ItemStatArtificing, "artificing")
mil.AddMappedItemStat(ItemStatArtistry, "artistry")
mil.AddMappedItemStat(ItemStatChemistry, "chemistry")
mil.AddMappedItemStat(ItemStatCrushing, "crushing")
mil.AddMappedItemStat(ItemStatDefense, "defense")
mil.AddMappedItemStat(ItemStatDeflection, "deflection")
mil.AddMappedItemStat(ItemStatDisruption, "disruption")
mil.AddMappedItemStat(ItemStatFishing, "fishing")
mil.AddMappedItemStat(ItemStatFletching, "fletching")
mil.AddMappedItemStat(ItemStatFocus, "focus")
mil.AddMappedItemStat(ItemStatForesting, "foresting")
mil.AddMappedItemStat(ItemStatGathering, "gathering")
mil.AddMappedItemStat(ItemStatMetalShaping, "metal shaping")
mil.AddMappedItemStat(ItemStatMetalworking, "metalworking")
mil.AddMappedItemStat(ItemStatMining, "mining")
mil.AddMappedItemStat(ItemStatMinistration, "ministration")
mil.AddMappedItemStat(ItemStatOrdination, "ordination")
mil.AddMappedItemStat(ItemStatParry, "parry")
mil.AddMappedItemStat(ItemStatPiercing, "piercing")
mil.AddMappedItemStat(ItemStatRanged, "ranged")
mil.AddMappedItemStat(ItemStatSafeFall, "safe fall")
mil.AddMappedItemStat(ItemStatScribing, "scribing")
mil.AddMappedItemStat(ItemStatSculpting, "sculpting")
mil.AddMappedItemStat(ItemStatSlashing, "slashing")
mil.AddMappedItemStat(ItemStatSubjugation, "subjugation")
mil.AddMappedItemStat(ItemStatSwimming, "swimming")
mil.AddMappedItemStat(ItemStatTailoring, "tailoring")
mil.AddMappedItemStat(ItemStatTinkering, "tinkering")
mil.AddMappedItemStat(ItemStatTransmuting, "transmuting")
mil.AddMappedItemStat(ItemStatTrapping, "trapping")
mil.AddMappedItemStat(ItemStatWeaponSkills, "weapon skills")
mil.AddMappedItemStat(ItemStatPowerCostReduction, "power cost reduction")
mil.AddMappedItemStat(ItemStatSpellAvoidance, "spell avoidance")
}
// AddMappedItemStat adds a mapping between stat ID and name
func (mil *MasterItemList) AddMappedItemStat(id int32, lowerCaseName string) {
mil.mutex.Lock()
defer mil.mutex.Unlock()
mil.mappedItemStatsStrings[lowerCaseName] = id
mil.mappedItemStatTypeIDs[id] = lowerCaseName
// log.Printf("Added stat mapping: %s -> %d", lowerCaseName, id)
}
// GetItemStatIDByName gets the stat ID by name
func (mil *MasterItemList) GetItemStatIDByName(name string) int32 {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
lowerName := strings.ToLower(name)
if id, exists := mil.mappedItemStatsStrings[lowerName]; exists {
return id
}
return 0
}
// GetMappedStatCount returns the number of mapped stats (for debugging)
func (mil *MasterItemList) GetMappedStatCount() int {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
return len(mil.mappedItemStatsStrings)
}
// GetItemStatNameByID gets the stat name by ID
func (mil *MasterItemList) GetItemStatNameByID(id int32) string {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
if name, exists := mil.mappedItemStatTypeIDs[id]; exists {
return name
}
return ""
}
// AddItem adds an item to the master list
func (mil *MasterItemList) AddItem(item *Item) {
if item == nil {
return
}
mil.mutex.Lock()
defer mil.mutex.Unlock()
mil.items[item.Details.ItemID] = item
// Added item to master list
}
// GetItem retrieves an item by ID
func (mil *MasterItemList) GetItem(itemID int32) *Item {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
if item, exists := mil.items[itemID]; exists {
return item.Copy() // Return a copy to prevent external modifications
}
return nil
}
// GetItemByName retrieves an item by name (case-insensitive)
func (mil *MasterItemList) GetItemByName(name string) *Item {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
lowerName := strings.ToLower(name)
for _, item := range mil.items {
if strings.ToLower(item.Name) == lowerName {
return item.Copy()
}
}
return nil
}
// IsBag checks if an item ID represents a bag
func (mil *MasterItemList) IsBag(itemID int32) bool {
item := mil.GetItem(itemID)
if item == nil {
return false
}
return item.IsBag()
}
// RemoveAll removes all items from the master list
func (mil *MasterItemList) RemoveAll() {
mil.mutex.Lock()
defer mil.mutex.Unlock()
count := len(mil.items)
mil.items = make(map[int32]*Item)
log.Printf("Removed %d items from master list", count)
}
// GetItemCount returns the total number of items
func (mil *MasterItemList) GetItemCount() int {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
return len(mil.items)
}
// CalculateItemBonuses calculates the stat bonuses for an item
func (mil *MasterItemList) CalculateItemBonuses(itemID int32) *ItemStatsValues {
item := mil.GetItem(itemID)
if item == nil {
return nil
}
return mil.CalculateItemBonusesFromItem(item, nil)
}
// CalculateItemBonusesWithEntity calculates the stat bonuses for an item with entity-specific modifiers
func (mil *MasterItemList) CalculateItemBonusesWithEntity(itemID int32, entity Entity) *ItemStatsValues {
item := mil.GetItem(itemID)
if item == nil {
return nil
}
return mil.CalculateItemBonusesFromItem(item, entity)
}
// CalculateItemBonusesFromItem calculates stat bonuses from an item instance
func (mil *MasterItemList) CalculateItemBonusesFromItem(item *Item, entity Entity) *ItemStatsValues {
if item == nil {
return nil
}
item.mutex.RLock()
defer item.mutex.RUnlock()
values := &ItemStatsValues{}
// Process all item stats
for _, stat := range item.ItemStats {
switch stat.StatType {
case ItemStatStr:
values.Str += int16(stat.Value)
case ItemStatSta:
values.Sta += int16(stat.Value)
case ItemStatAgi:
values.Agi += int16(stat.Value)
case ItemStatWis:
values.Wis += int16(stat.Value)
case ItemStatInt:
values.Int += int16(stat.Value)
case ItemStatVsSlash:
values.VsSlash += int16(stat.Value)
case ItemStatVsCrush:
values.VsCrush += int16(stat.Value)
case ItemStatVsPierce:
values.VsPierce += int16(stat.Value)
case ItemStatVsPhysical:
values.VsPhysical += int16(stat.Value)
case ItemStatVsHeat:
values.VsHeat += int16(stat.Value)
case ItemStatVsCold:
values.VsCold += int16(stat.Value)
case ItemStatVsMagic:
values.VsMagic += int16(stat.Value)
case ItemStatVsMental:
values.VsMental += int16(stat.Value)
case ItemStatVsDivine:
values.VsDivine += int16(stat.Value)
case ItemStatVsDisease:
values.VsDisease += int16(stat.Value)
case ItemStatVsPoison:
values.VsPoison += int16(stat.Value)
case ItemStatHealth:
values.Health += int16(stat.Value)
case ItemStatPower:
values.Power += int16(stat.Value)
case ItemStatConcentration:
values.Concentration += int8(stat.Value)
case ItemStatAbilityModifier:
values.AbilityModifier += int16(stat.Value)
case ItemStatCriticalMitigation:
values.CriticalMitigation += int16(stat.Value)
case ItemStatExtraShieldBlockChance:
values.ExtraShieldBlockChance += int16(stat.Value)
case ItemStatBeneficialCritChance:
values.BeneficialCritChance += int16(stat.Value)
case ItemStatCritBonus:
values.CritBonus += int16(stat.Value)
case ItemStatPotency:
values.Potency += int16(stat.Value)
case ItemStatHateGainMod:
values.HateGainMod += int16(stat.Value)
case ItemStatAbilityReuseSpeed:
values.AbilityReuseSpeed += int16(stat.Value)
case ItemStatAbilityCastingSpeed:
values.AbilityCastingSpeed += int16(stat.Value)
case ItemStatAbilityRecoverySpeed:
values.AbilityRecoverySpeed += int16(stat.Value)
case ItemStatSpellReuseSpeed:
values.SpellReuseSpeed += int16(stat.Value)
case ItemStatSpellMultiAttackChance:
values.SpellMultiAttackChance += int16(stat.Value)
case ItemStatDPS:
values.DPS += int16(stat.Value)
case ItemStatAttackSpeed:
values.AttackSpeed += int16(stat.Value)
case ItemStatMultiattackChance:
values.MultiAttackChance += int16(stat.Value)
case ItemStatFlurry:
values.Flurry += int16(stat.Value)
case ItemStatAEAutoattackChance:
values.AEAutoattackChance += int16(stat.Value)
case ItemStatStrikethrough:
values.Strikethrough += int16(stat.Value)
case ItemStatAccuracy:
values.Accuracy += int16(stat.Value)
case ItemStatOffensiveSpeed:
values.OffensiveSpeed += int16(stat.Value)
case ItemStatUncontestedParry:
values.UncontestedParry += stat.Value
case ItemStatUncontestedBlock:
values.UncontestedBlock += stat.Value
case ItemStatUncontestedDodge:
values.UncontestedDodge += stat.Value
case ItemStatUncontestedRiposte:
values.UncontestedRiposte += stat.Value
case ItemStatSizeMod:
values.SizeMod += stat.Value
}
}
return values
}
// Broker-related methods
// AddBrokerItemMapRange adds a broker item mapping range
func (mil *MasterItemList) AddBrokerItemMapRange(minVersion int32, maxVersion int32, clientBitmask int64, serverBitmask int64) {
mil.mutex.Lock()
defer mil.mutex.Unlock()
// Find existing range
var targetRange *VersionRange
for versionRange := range mil.brokerItemMap {
if versionRange.MinVersion == minVersion && versionRange.MaxVersion == maxVersion {
targetRange = versionRange
break
}
}
// Create new range if not found
if targetRange == nil {
targetRange = &VersionRange{
MinVersion: minVersion,
MaxVersion: maxVersion,
}
mil.brokerItemMap[targetRange] = make(map[int64]int64)
}
mil.brokerItemMap[targetRange][clientBitmask] = serverBitmask
}
// FindBrokerItemMapVersionRange finds a broker item map by version range
func (mil *MasterItemList) FindBrokerItemMapVersionRange(minVersion int32, maxVersion int32) map[int64]int64 {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
for versionRange, mapping := range mil.brokerItemMap {
// Check if min and max version are both in range
if versionRange.MinVersion <= minVersion && maxVersion <= versionRange.MaxVersion {
return mapping
}
// Check if the min version is in range, but max range is 0
if versionRange.MinVersion <= minVersion && versionRange.MaxVersion == 0 {
return mapping
}
// Check if min version is 0 and max_version has a cap
if versionRange.MinVersion == 0 && maxVersion <= versionRange.MaxVersion {
return mapping
}
}
return nil
}
// FindBrokerItemMapByVersion finds a broker item map by specific version
func (mil *MasterItemList) FindBrokerItemMapByVersion(version int32) map[int64]int64 {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
var defaultMapping map[int64]int64
for versionRange, mapping := range mil.brokerItemMap {
// Check for default range (0,0)
if versionRange.MinVersion == 0 && versionRange.MaxVersion == 0 {
defaultMapping = mapping
continue
}
// Check if version is in range
if version >= versionRange.MinVersion && version <= versionRange.MaxVersion {
return mapping
}
}
return defaultMapping
}
// ShouldAddItemBrokerType checks if an item should be added to broker by type
func (mil *MasterItemList) ShouldAddItemBrokerType(item *Item, itemType int64) bool {
if item == nil {
return false
}
switch itemType {
case ItemBrokerTypeAdornment:
return item.IsAdornment()
case ItemBrokerTypeAmmo:
return item.IsAmmo()
case ItemBrokerTypeAttuneable:
return item.CheckFlag(Attuneable)
case ItemBrokerTypeBag:
return item.IsBag()
case ItemBrokerTypeBauble:
return item.IsBauble()
case ItemBrokerTypeBook:
return item.IsBook()
case ItemBrokerTypeChainarmor:
return item.IsChainArmor()
case ItemBrokerTypeCloak:
return item.IsCloak()
case ItemBrokerTypeClotharmor:
return item.IsClothArmor()
case ItemBrokerTypeCollectable:
return item.IsCollectable()
case ItemBrokerTypeCrushweapon:
return item.IsCrushWeapon()
case ItemBrokerTypeDrink:
return item.IsFoodDrink()
case ItemBrokerTypeFood:
return item.IsFoodFood()
case ItemBrokerTypeHouseitem:
return item.IsHouseItem()
case ItemBrokerTypeJewelry:
return item.IsJewelry()
case ItemBrokerTypeLeatherarmor:
return item.IsLeatherArmor()
case ItemBrokerTypeLore:
return item.CheckFlag(Lore)
case ItemBrokerTypeMisc:
return item.IsMisc()
case ItemBrokerTypePierceweapon:
return item.IsPierceWeapon()
case ItemBrokerTypePlatearmor:
return item.IsPlateArmor()
case ItemBrokerTypePoison:
return item.IsPoison()
case ItemBrokerTypePotion:
return item.IsPotion()
case ItemBrokerTypeRecipebook:
return item.IsRecipeBook()
case ItemBrokerTypeSalesdisplay:
return item.IsSalesDisplay()
case ItemBrokerTypeShield:
return item.IsShield()
case ItemBrokerTypeSlashweapon:
return item.IsSlashWeapon()
case ItemBrokerTypeSpellscroll:
return item.IsSpellScroll()
case ItemBrokerTypeTinkered:
return item.IsTinkered()
case ItemBrokerTypeTradeskill:
return item.IsTradeskill()
}
return false
}
// ShouldAddItemBrokerSlot checks if an item should be added to broker by slot
func (mil *MasterItemList) ShouldAddItemBrokerSlot(item *Item, slotType int64) bool {
if item == nil {
return false
}
switch slotType {
case ItemBrokerSlotPrimary:
return item.HasSlot(EQ2PrimarySlot, -1)
case ItemBrokerSlotPrimary2H:
return item.HasSlot(EQ2PrimarySlot, -1) || item.HasSlot(EQ2SecondarySlot, -1)
case ItemBrokerSlotSecondary:
return item.HasSlot(EQ2SecondarySlot, -1)
case ItemBrokerSlotHead:
return item.HasSlot(EQ2HeadSlot, -1)
case ItemBrokerSlotChest:
return item.HasSlot(EQ2ChestSlot, -1)
case ItemBrokerSlotShoulders:
return item.HasSlot(EQ2ShouldersSlot, -1)
case ItemBrokerSlotForearms:
return item.HasSlot(EQ2ForearmsSlot, -1)
case ItemBrokerSlotHands:
return item.HasSlot(EQ2HandsSlot, -1)
case ItemBrokerSlotLegs:
return item.HasSlot(EQ2LegsSlot, -1)
case ItemBrokerSlotFeet:
return item.HasSlot(EQ2FeetSlot, -1)
case ItemBrokerSlotRing:
return item.HasSlot(EQ2LRingSlot, EQ2RRingSlot)
case ItemBrokerSlotEars:
return item.HasSlot(EQ2EarsSlot1, EQ2EarsSlot2)
case ItemBrokerSlotNeck:
return item.HasSlot(EQ2NeckSlot, -1)
case ItemBrokerSlotWrist:
return item.HasSlot(EQ2LWristSlot, EQ2RWristSlot)
case ItemBrokerSlotRangeWeapon:
return item.HasSlot(EQ2RangeSlot, -1)
case ItemBrokerSlotAmmo:
return item.HasSlot(EQ2AmmoSlot, -1)
case ItemBrokerSlotWaist:
return item.HasSlot(EQ2WaistSlot, -1)
case ItemBrokerSlotCloak:
return item.HasSlot(EQ2CloakSlot, -1)
case ItemBrokerSlotCharm:
return item.HasSlot(EQ2CharmSlot1, EQ2CharmSlot2)
case ItemBrokerSlotFood:
return item.HasSlot(EQ2FoodSlot, -1)
case ItemBrokerSlotDrink:
return item.HasSlot(EQ2DrinkSlot, -1)
}
return false
}
// ShouldAddItemBrokerStat checks if an item should be added to broker by stat
func (mil *MasterItemList) ShouldAddItemBrokerStat(item *Item, statType int64) bool {
if item == nil {
return false
}
// Check if the item has the requested stat type
for _, stat := range item.ItemStats {
switch statType {
case ItemBrokerStatTypeStr:
if stat.StatType == ItemStatStr {
return true
}
case ItemBrokerStatTypeSta:
if stat.StatType == ItemStatSta {
return true
}
case ItemBrokerStatTypeAgi:
if stat.StatType == ItemStatAgi {
return true
}
case ItemBrokerStatTypeWis:
if stat.StatType == ItemStatWis {
return true
}
case ItemBrokerStatTypeInt:
if stat.StatType == ItemStatInt {
return true
}
case ItemBrokerStatTypeHealth:
if stat.StatType == ItemStatHealth {
return true
}
case ItemBrokerStatTypePower:
if stat.StatType == ItemStatPower {
return true
}
case ItemBrokerStatTypePotency:
if stat.StatType == ItemStatPotency {
return true
}
case ItemBrokerStatTypeCritical:
if stat.StatType == ItemStatMeleeCritChance || stat.StatType == ItemStatBeneficialCritChance {
return true
}
case ItemBrokerStatTypeAttackspeed:
if stat.StatType == ItemStatAttackSpeed {
return true
}
case ItemBrokerStatTypeDPS:
if stat.StatType == ItemStatDPS {
return true
}
// Add more stat type checks as needed
}
}
return false
}
// GetItems searches for items based on criteria
func (mil *MasterItemList) GetItems(criteria *ItemSearchCriteria) []*Item {
if criteria == nil {
return nil
}
mil.mutex.RLock()
defer mil.mutex.RUnlock()
var results []*Item
for _, item := range mil.items {
if mil.matchesCriteria(item, criteria) {
results = append(results, item.Copy())
}
}
return results
}
// matchesCriteria checks if an item matches the search criteria
func (mil *MasterItemList) matchesCriteria(item *Item, criteria *ItemSearchCriteria) bool {
// Name matching
if criteria.Name != "" {
if !strings.Contains(strings.ToLower(item.Name), strings.ToLower(criteria.Name)) {
return false
}
}
// Price range
if criteria.MinPrice > 0 && item.BrokerPrice < criteria.MinPrice {
return false
}
if criteria.MaxPrice > 0 && item.BrokerPrice > criteria.MaxPrice {
return false
}
// Tier range
if criteria.MinTier > 0 && item.Details.Tier < criteria.MinTier {
return false
}
if criteria.MaxTier > 0 && item.Details.Tier > criteria.MaxTier {
return false
}
// Level range
if criteria.MinLevel > 0 && item.Details.RecommendedLevel < criteria.MinLevel {
return false
}
if criteria.MaxLevel > 0 && item.Details.RecommendedLevel > criteria.MaxLevel {
return false
}
// Item type matching
if criteria.ItemType != 0 {
if !mil.ShouldAddItemBrokerType(item, criteria.ItemType) {
return false
}
}
// Location type matching (slot compatibility)
if criteria.LocationType != 0 {
if !mil.ShouldAddItemBrokerSlot(item, criteria.LocationType) {
return false
}
}
// Broker type matching (stat requirements)
if criteria.BrokerType != 0 {
if !mil.ShouldAddItemBrokerStat(item, criteria.BrokerType) {
return false
}
}
// Seller matching
if criteria.Seller != "" {
if !strings.Contains(strings.ToLower(item.SellerName), strings.ToLower(criteria.Seller)) {
return false
}
}
// Adornment matching
if criteria.Adornment != "" {
if !strings.Contains(strings.ToLower(item.Adornment), strings.ToLower(criteria.Adornment)) {
return false
}
}
return true
}
// GetStats returns statistics about the master item list
func (mil *MasterItemList) GetStats() *ItemManagerStats {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
stats := &ItemManagerStats{
TotalItems: int32(len(mil.items)),
ItemsByType: make(map[int8]int32),
ItemsByTier: make(map[int8]int32),
LastUpdate: time.Now(),
}
// Count items by type and tier
for _, item := range mil.items {
stats.ItemsByType[item.GenericInfo.ItemType]++
stats.ItemsByTier[item.Details.Tier]++
}
return stats
}
// Validate validates the master item list
func (mil *MasterItemList) Validate() *ItemValidationResult {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
result := &ItemValidationResult{Valid: true}
for itemID, item := range mil.items {
itemResult := item.Validate()
if !itemResult.Valid {
result.Valid = false
for _, err := range itemResult.Errors {
result.Errors = append(result.Errors, fmt.Sprintf("Item %d: %s", itemID, err))
}
}
}
return result
}
// Size returns the number of items in the master list
func (mil *MasterItemList) Size() int {
return mil.GetItemCount()
}
// Clear removes all items from the master list
func (mil *MasterItemList) Clear() {
mil.RemoveAll()
}
func init() {
// Master item list system initialized
}