eq2go/internal/items/interfaces.go

744 lines
21 KiB
Go

package items
import (
"fmt"
"log"
"sync"
"time"
)
// SpellManager defines the interface for spell-related operations needed by items
type SpellManager interface {
// GetSpell retrieves spell information by ID and tier
GetSpell(spellID uint32, tier int8) (Spell, error)
// GetSpellsBySkill gets spells associated with a skill
GetSpellsBySkill(skillID uint32) ([]uint32, error)
// ValidateSpellID checks if a spell ID is valid
ValidateSpellID(spellID uint32) bool
}
// PlayerManager defines the interface for player-related operations needed by items
type PlayerManager interface {
// GetPlayer retrieves player information by ID
GetPlayer(playerID uint32) (Player, error)
// GetPlayerLevel gets a player's current level
GetPlayerLevel(playerID uint32) (int16, error)
// GetPlayerClass gets a player's adventure class
GetPlayerClass(playerID uint32) (int8, error)
// GetPlayerRace gets a player's race
GetPlayerRace(playerID uint32) (int8, error)
// SendMessageToPlayer sends a message to a player
SendMessageToPlayer(playerID uint32, channel int8, message string) error
// GetPlayerName gets a player's name
GetPlayerName(playerID uint32) (string, error)
}
// PacketManager defines the interface for packet-related operations
type PacketManager interface {
// SendPacketToPlayer sends a packet to a specific player
SendPacketToPlayer(playerID uint32, packetData []byte) error
// QueuePacketForPlayer queues a packet for delayed sending
QueuePacketForPlayer(playerID uint32, packetData []byte) error
// GetClientVersion gets the client version for a player
GetClientVersion(playerID uint32) (int16, error)
// SerializeItem serializes an item for network transmission
SerializeItem(item *Item, clientVersion int16, player Player) ([]byte, error)
}
// RuleManager defines the interface for rules/configuration access
type RuleManager interface {
// GetBool retrieves a boolean rule value
GetBool(category, rule string) bool
// GetInt32 retrieves an int32 rule value
GetInt32(category, rule string) int32
// GetFloat retrieves a float rule value
GetFloat(category, rule string) float32
// GetString retrieves a string rule value
GetString(category, rule string) string
}
// DatabaseService defines the interface for item persistence operations
type DatabaseService interface {
// LoadItems loads all item templates from the database
LoadItems(masterList *MasterItemList) error
// SaveItem saves an item template to the database
SaveItem(item *Item) error
// DeleteItem removes an item template from the database
DeleteItem(itemID int32) error
// LoadPlayerItems loads a player's inventory from the database
LoadPlayerItems(playerID uint32) (*PlayerItemList, error)
// SavePlayerItems saves a player's inventory to the database
SavePlayerItems(playerID uint32, itemList *PlayerItemList) error
// LoadPlayerEquipment loads a player's equipment from the database
LoadPlayerEquipment(playerID uint32, appearanceType int8) (*EquipmentItemList, error)
// SavePlayerEquipment saves a player's equipment to the database
SavePlayerEquipment(playerID uint32, equipment *EquipmentItemList) error
// LoadItemStats loads item stat mappings from the database
LoadItemStats() (map[string]int32, map[int32]string, error)
// SaveItemStat saves an item stat mapping to the database
SaveItemStat(statID int32, statName string) error
}
// QuestManager defines the interface for quest-related item operations
type QuestManager interface {
// CheckQuestPrerequisites checks if a player meets quest prerequisites for an item
CheckQuestPrerequisites(playerID uint32, questID int32) bool
// GetQuestRewards gets quest rewards for an item
GetQuestRewards(questID int32) ([]*QuestRewardData, error)
// IsQuestItem checks if an item is a quest item
IsQuestItem(itemID int32) bool
}
// BrokerManager defines the interface for broker/marketplace operations
type BrokerManager interface {
// SearchItems searches for items on the broker
SearchItems(criteria *ItemSearchCriteria) ([]*Item, error)
// ListItem lists an item on the broker
ListItem(playerID uint32, item *Item, price int64) error
// BuyItem purchases an item from the broker
BuyItem(playerID uint32, itemID int32, sellerID uint32) error
// GetItemPrice gets the current market price for an item
GetItemPrice(itemID int32) (int64, error)
}
// CraftingManager defines the interface for crafting-related item operations
type CraftingManager interface {
// CanCraftItem checks if a player can craft an item
CanCraftItem(playerID uint32, itemID int32) bool
// GetCraftingRequirements gets crafting requirements for an item
GetCraftingRequirements(itemID int32) ([]CraftingRequirement, error)
// CraftItem handles item crafting
CraftItem(playerID uint32, itemID int32, quality int8) (*Item, error)
}
// HousingManager defines the interface for housing-related item operations
type HousingManager interface {
// CanPlaceItem checks if an item can be placed in a house
CanPlaceItem(playerID uint32, houseID int32, item *Item) bool
// PlaceItem places an item in a house
PlaceItem(playerID uint32, houseID int32, item *Item, location HouseLocation) error
// RemoveItem removes an item from a house
RemoveItem(playerID uint32, houseID int32, itemID int32) error
// GetHouseItems gets all items in a house
GetHouseItems(houseID int32) ([]*Item, error)
}
// LootManager defines the interface for loot-related operations
type LootManager interface {
// GenerateLoot generates loot for a loot table
GenerateLoot(lootTableID int32, playerLevel int16) ([]*Item, error)
// DistributeLoot distributes loot to players
DistributeLoot(items []*Item, playerIDs []uint32, lootMethod int8) error
// CanLootItem checks if a player can loot an item
CanLootItem(playerID uint32, item *Item) bool
}
// Data structures used by the interfaces
// Spell represents a spell in the game
type Spell interface {
GetID() uint32
GetName() string
GetIcon() uint32
GetIconBackdrop() uint32
GetTier() int8
GetDescription() string
}
// Player represents a player in the game
type Player interface {
GetID() uint32
GetName() string
GetLevel() int16
GetAdventureClass() int8
GetTradeskillClass() int8
GetRace() int8
GetGender() int8
GetAlignment() int8
}
// Entity represents an entity (player or NPC) that can have items
type Entity interface {
GetID() uint32
GetName() string
GetLevel() int16
GetRace() int8
GetGender() int8
GetAlignment() int8
IsPlayer() bool
IsNPC() bool
// GetStatValueByName gets a stat value by name for item calculations
GetStatValueByName(statName string) float64
// GetSkillValueByName gets a skill value by name for item calculations
GetSkillValueByName(skillName string) int32
}
// CraftingRequirement represents a crafting requirement
type CraftingRequirement struct {
ItemID int32 `json:"item_id"`
Quantity int16 `json:"quantity"`
Skill int32 `json:"skill"`
Level int16 `json:"level"`
}
// HouseLocation represents a location within a house
type HouseLocation struct {
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
Heading float32 `json:"heading"`
Pitch float32 `json:"pitch"`
Roll float32 `json:"roll"`
Location int8 `json:"location"` // 0=floor, 1=ceiling, 2=wall
}
// ItemSystemAdapter provides a high-level interface to the complete item system
type ItemSystemAdapter struct {
masterList *MasterItemList
playerLists map[uint32]*PlayerItemList
equipmentLists map[uint32]*EquipmentItemList
spellManager SpellManager
playerManager PlayerManager
packetManager PacketManager
ruleManager RuleManager
databaseService DatabaseService
questManager QuestManager
brokerManager BrokerManager
craftingManager CraftingManager
housingManager HousingManager
lootManager LootManager
mutex sync.RWMutex
}
// NewItemSystemAdapter creates a new item system adapter with all dependencies
func NewItemSystemAdapter(
masterList *MasterItemList,
spellManager SpellManager,
playerManager PlayerManager,
packetManager PacketManager,
ruleManager RuleManager,
databaseService DatabaseService,
questManager QuestManager,
brokerManager BrokerManager,
craftingManager CraftingManager,
housingManager HousingManager,
lootManager LootManager,
) *ItemSystemAdapter {
return &ItemSystemAdapter{
masterList: masterList,
playerLists: make(map[uint32]*PlayerItemList),
equipmentLists: make(map[uint32]*EquipmentItemList),
spellManager: spellManager,
playerManager: playerManager,
packetManager: packetManager,
ruleManager: ruleManager,
databaseService: databaseService,
questManager: questManager,
brokerManager: brokerManager,
craftingManager: craftingManager,
housingManager: housingManager,
lootManager: lootManager,
}
}
// Initialize sets up the item system (loads items from database, etc.)
func (isa *ItemSystemAdapter) Initialize() error {
// Load items from database
err := isa.databaseService.LoadItems(isa.masterList)
if err != nil {
return err
}
// Load item stat mappings
statsStrings, statsIDs, err := isa.databaseService.LoadItemStats()
if err != nil {
return err
}
isa.masterList.mutex.Lock()
isa.masterList.mappedItemStatsStrings = statsStrings
isa.masterList.mappedItemStatTypeIDs = statsIDs
isa.masterList.mutex.Unlock()
return nil
}
// GetPlayerInventory gets or loads a player's inventory
func (isa *ItemSystemAdapter) GetPlayerInventory(playerID uint32) (*PlayerItemList, error) {
isa.mutex.Lock()
defer isa.mutex.Unlock()
if itemList, exists := isa.playerLists[playerID]; exists {
return itemList, nil
}
// Load from database
itemList, err := isa.databaseService.LoadPlayerItems(playerID)
if err != nil {
return nil, err
}
if itemList == nil {
itemList = NewPlayerItemList()
}
isa.playerLists[playerID] = itemList
return itemList, nil
}
// GetPlayerEquipment gets or loads a player's equipment
func (isa *ItemSystemAdapter) GetPlayerEquipment(playerID uint32, appearanceType int8) (*EquipmentItemList, error) {
isa.mutex.Lock()
defer isa.mutex.Unlock()
key := uint32(playerID)*10 + uint32(appearanceType)
if equipment, exists := isa.equipmentLists[key]; exists {
return equipment, nil
}
// Load from database
equipment, err := isa.databaseService.LoadPlayerEquipment(playerID, appearanceType)
if err != nil {
return nil, err
}
if equipment == nil {
equipment = NewEquipmentItemList()
equipment.SetAppearanceType(appearanceType)
}
isa.equipmentLists[key] = equipment
return equipment, nil
}
// SavePlayerData saves a player's item data
func (isa *ItemSystemAdapter) SavePlayerData(playerID uint32) error {
isa.mutex.RLock()
defer isa.mutex.RUnlock()
// Save inventory
if itemList, exists := isa.playerLists[playerID]; exists {
err := isa.databaseService.SavePlayerItems(playerID, itemList)
if err != nil {
return err
}
}
// Save equipment (both normal and appearance)
for key, equipment := range isa.equipmentLists {
if key/10 == playerID {
err := isa.databaseService.SavePlayerEquipment(playerID, equipment)
if err != nil {
return err
}
}
}
return nil
}
// GiveItemToPlayer gives an item to a player
func (isa *ItemSystemAdapter) GiveItemToPlayer(playerID uint32, itemID int32, quantity int16, addType AddItemType) error {
// Get item template
itemTemplate := isa.masterList.GetItem(itemID)
if itemTemplate == nil {
return ErrItemNotFound
}
// Create item instance
item := NewItemFromTemplate(itemTemplate)
item.Details.Count = quantity
// Get player inventory
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
// Try to add item to inventory
if !inventory.AddItem(item) {
return ErrInsufficientSpace
}
// Send update to player
player, err := isa.playerManager.GetPlayer(playerID)
if err != nil {
return err
}
clientVersion, _ := isa.packetManager.GetClientVersion(playerID)
packetData, err := isa.packetManager.SerializeItem(item, clientVersion, player)
if err != nil {
return err
}
return isa.packetManager.SendPacketToPlayer(playerID, packetData)
}
// RemoveItemFromPlayer removes an item from a player
func (isa *ItemSystemAdapter) RemoveItemFromPlayer(playerID uint32, uniqueID int32, quantity int16) error {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
item := inventory.GetItemFromUniqueID(uniqueID, true, true)
if item == nil {
return ErrItemNotFound
}
// Check if item can be removed
if item.IsItemLocked() {
return ErrItemLocked
}
if item.Details.Count <= quantity {
// Remove entire stack
inventory.RemoveItem(item, true, true)
} else {
// Reduce quantity
item.Details.Count -= quantity
}
return nil
}
// EquipItem equips an item for a player
func (isa *ItemSystemAdapter) EquipItem(playerID uint32, uniqueID int32, slot int8, appearanceType int8) error {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
equipment, err := isa.GetPlayerEquipment(playerID, appearanceType)
if err != nil {
return err
}
// Get item from inventory
item := inventory.GetItemFromUniqueID(uniqueID, false, true)
if item == nil {
return ErrItemNotFound
}
// Check if item can be equipped
if !equipment.CanItemBeEquippedInSlot(item, slot) {
return ErrCannotEquip
}
// Check class/race/level requirements
player, err := isa.playerManager.GetPlayer(playerID)
if err != nil {
return err
}
if !item.CheckClass(player.GetAdventureClass(), player.GetTradeskillClass()) {
return ErrCannotEquip
}
if !item.CheckClassLevel(player.GetAdventureClass(), player.GetTradeskillClass(), player.GetLevel()) {
return ErrCannotEquip
}
// Remove from inventory
inventory.RemoveItem(item, false, true)
// Check if slot is occupied and unequip current item
currentItem := equipment.GetItem(slot)
if currentItem != nil {
equipment.RemoveItem(slot, false)
inventory.AddItem(currentItem)
}
// Equip new item
equipment.SetItem(slot, item, false)
return nil
}
// UnequipItem unequips an item for a player
func (isa *ItemSystemAdapter) UnequipItem(playerID uint32, slot int8, appearanceType int8) error {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
equipment, err := isa.GetPlayerEquipment(playerID, appearanceType)
if err != nil {
return err
}
// Get equipped item
item := equipment.GetItem(slot)
if item == nil {
return ErrItemNotFound
}
// Check if item can be unequipped
if item.IsItemLocked() {
return ErrItemLocked
}
// Remove from equipment
equipment.RemoveItem(slot, false)
// Add to inventory
if !inventory.AddItem(item) {
// Inventory full, add to overflow
inventory.AddOverflowItem(item)
}
return nil
}
// MoveItem moves an item within a player's inventory
func (isa *ItemSystemAdapter) MoveItem(playerID uint32, fromBagID int32, fromSlot int16, toBagID int32, toSlot int16, appearanceType int8) error {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
// Get item from source location
item := inventory.GetItem(fromBagID, fromSlot, appearanceType)
if item == nil {
return ErrItemNotFound
}
// Check if item is locked
if item.IsItemLocked() {
return ErrItemLocked
}
// Move item
inventory.MoveItem(item, toBagID, toSlot, appearanceType, true)
return nil
}
// SearchBrokerItems searches for items on the broker
func (isa *ItemSystemAdapter) SearchBrokerItems(criteria *ItemSearchCriteria) ([]*Item, error) {
if isa.brokerManager == nil {
return nil, fmt.Errorf("broker manager not available")
}
return isa.brokerManager.SearchItems(criteria)
}
// CraftItem handles item crafting
func (isa *ItemSystemAdapter) CraftItem(playerID uint32, itemID int32, quality int8) (*Item, error) {
if isa.craftingManager == nil {
return nil, fmt.Errorf("crafting manager not available")
}
// Check if player can craft the item
if !isa.craftingManager.CanCraftItem(playerID, itemID) {
return nil, fmt.Errorf("player cannot craft this item")
}
// Craft the item
return isa.craftingManager.CraftItem(playerID, itemID, quality)
}
// GetPlayerItemStats returns statistics about a player's items
func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]interface{}, error) {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return nil, err
}
equipment, err := isa.GetPlayerEquipment(playerID, BaseEquipment)
if err != nil {
return nil, err
}
// Calculate equipment bonuses
bonuses := equipment.CalculateEquipmentBonuses()
return map[string]interface{}{
"player_id": playerID,
"total_items": inventory.GetNumberOfItems(),
"equipped_items": equipment.GetNumberOfItems(),
"inventory_weight": inventory.GetWeight(),
"equipment_weight": equipment.GetWeight(),
"free_slots": inventory.GetNumberOfFreeSlots(),
"overflow_items": len(inventory.GetOverflowItemList()),
"stat_bonuses": bonuses,
"last_update": time.Now(),
}, nil
}
// GetSystemStats returns comprehensive statistics about the item system
func (isa *ItemSystemAdapter) GetSystemStats() map[string]interface{} {
isa.mutex.RLock()
defer isa.mutex.RUnlock()
masterStats := isa.masterList.GetStats()
return map[string]interface{}{
"total_item_templates": masterStats.TotalItems,
"items_by_type": masterStats.ItemsByType,
"items_by_tier": masterStats.ItemsByTier,
"active_players": len(isa.playerLists),
"cached_inventories": len(isa.playerLists),
"cached_equipment": len(isa.equipmentLists),
"last_update": time.Now(),
}
}
// ClearPlayerData removes cached data for a player (e.g., when they log out)
func (isa *ItemSystemAdapter) ClearPlayerData(playerID uint32) {
isa.mutex.Lock()
defer isa.mutex.Unlock()
// Remove inventory
delete(isa.playerLists, playerID)
// Remove equipment
keysToDelete := make([]uint32, 0)
for key := range isa.equipmentLists {
if key/10 == playerID {
keysToDelete = append(keysToDelete, key)
}
}
for _, key := range keysToDelete {
delete(isa.equipmentLists, key)
}
}
// ValidatePlayerItems validates all items for a player
func (isa *ItemSystemAdapter) ValidatePlayerItems(playerID uint32) *ItemValidationResult {
result := &ItemValidationResult{Valid: true}
// Validate inventory
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
result.Valid = false
result.Errors = append(result.Errors, fmt.Sprintf("Failed to load inventory: %v", err))
return result
}
allItems := inventory.GetAllItems()
for index, item := range allItems {
itemResult := item.Validate()
if !itemResult.Valid {
result.Valid = false
for _, itemErr := range itemResult.Errors {
result.Errors = append(result.Errors, fmt.Sprintf("Inventory item %d: %s", index, itemErr))
}
}
}
// Validate equipment
equipment, err := isa.GetPlayerEquipment(playerID, BaseEquipment)
if err != nil {
result.Valid = false
result.Errors = append(result.Errors, fmt.Sprintf("Failed to load equipment: %v", err))
return result
}
equipResult := equipment.ValidateEquipment()
if !equipResult.Valid {
result.Valid = false
result.Errors = append(result.Errors, equipResult.Errors...)
}
return result
}
// MockImplementations for testing
// MockSpellManager is a mock implementation of SpellManager for testing
type MockSpellManager struct {
spells map[uint32]MockSpell
}
// MockSpell is a mock implementation of Spell for testing
type MockSpell struct {
id uint32
name string
icon uint32
iconBackdrop uint32
tier int8
description string
}
func (ms MockSpell) GetID() uint32 { return ms.id }
func (ms MockSpell) GetName() string { return ms.name }
func (ms MockSpell) GetIcon() uint32 { return ms.icon }
func (ms MockSpell) GetIconBackdrop() uint32 { return ms.iconBackdrop }
func (ms MockSpell) GetTier() int8 { return ms.tier }
func (ms MockSpell) GetDescription() string { return ms.description }
func (msm *MockSpellManager) GetSpell(spellID uint32, tier int8) (Spell, error) {
if spell, exists := msm.spells[spellID]; exists {
return spell, nil
}
return nil, fmt.Errorf("spell not found: %d", spellID)
}
func (msm *MockSpellManager) GetSpellsBySkill(skillID uint32) ([]uint32, error) {
return []uint32{}, nil
}
func (msm *MockSpellManager) ValidateSpellID(spellID uint32) bool {
_, exists := msm.spells[spellID]
return exists
}
// NewMockSpellManager creates a new mock spell manager
func NewMockSpellManager() *MockSpellManager {
return &MockSpellManager{
spells: make(map[uint32]MockSpell),
}
}
// AddMockSpell adds a mock spell for testing
func (msm *MockSpellManager) AddMockSpell(id uint32, name string, icon uint32, tier int8, description string) {
msm.spells[id] = MockSpell{
id: id,
name: name,
icon: icon,
iconBackdrop: icon + 1000,
tier: tier,
description: description,
}
}
func init() {
log.Printf("Item system interfaces initialized")
}