add item support to world server, first pass

This commit is contained in:
Sky Johnson 2025-08-07 00:21:35 -05:00
parent 80e3bf39b4
commit 31dbfa0fc3
5 changed files with 1019 additions and 2 deletions

View File

@ -81,6 +81,15 @@ const (
OP_NPCSpellCastMsg
OP_NPCMovementMsg
// Item system
OP_ItemMoveMsg
OP_ItemEquipMsg
OP_ItemUnequipMsg
OP_ItemPickupMsg
OP_ItemDropMsg
OP_ItemExamineMsg
OP_ItemUpdateMsg
// EverQuest specific commands - Core
OP_EqHearChatCmd
OP_EqDisplayTextCmd
@ -143,6 +152,13 @@ var OpcodeNames = map[InternalOpcode]string{
OP_NPCInfoMsg: "OP_NPCInfoMsg",
OP_NPCSpellCastMsg: "OP_NPCSpellCastMsg",
OP_NPCMovementMsg: "OP_NPCMovementMsg",
OP_ItemMoveMsg: "OP_ItemMoveMsg",
OP_ItemEquipMsg: "OP_ItemEquipMsg",
OP_ItemUnequipMsg: "OP_ItemUnequipMsg",
OP_ItemPickupMsg: "OP_ItemPickupMsg",
OP_ItemDropMsg: "OP_ItemDropMsg",
OP_ItemExamineMsg: "OP_ItemExamineMsg",
OP_ItemUpdateMsg: "OP_ItemUpdateMsg",
OP_EqHearChatCmd: "OP_EqHearChatCmd",
OP_EqDisplayTextCmd: "OP_EqDisplayTextCmd",
OP_EqCreateGhostCmd: "OP_EqCreateGhostCmd",

View File

@ -0,0 +1,679 @@
package world
import (
"fmt"
"sync"
"eq2emu/internal/database"
"eq2emu/internal/items"
)
// ItemManager manages items for the world server
type ItemManager struct {
masterItemList *items.MasterItemList
itemSystemAdapter *items.ItemSystemAdapter
database *database.Database
world *World // Reference to world server
// World-specific item tracking
playerInventories map[uint32]*items.PlayerItemList // Player ID -> Inventory
playerEquipment map[uint32]*items.EquipmentItemList // Player ID -> Equipment
worldDrops map[int32][]*items.Item // Zone ID -> Ground items
mutex sync.RWMutex
}
// NewItemManager creates a new item manager for the world server
func NewItemManager(db *database.Database) *ItemManager {
// Create master item list
masterList := items.NewMasterItemList()
// Create adapters for the item system
dbAdapter := &WorldItemDatabaseAdapter{db: db}
playerAdapter := &WorldItemPlayerAdapter{}
packetAdapter := &WorldItemPacketAdapter{}
ruleAdapter := &WorldItemRuleAdapter{}
// Create mock adapters for optional dependencies
questAdapter := &MockItemQuestAdapter{}
brokerAdapter := &MockItemBrokerAdapter{}
craftingAdapter := &MockItemCraftingAdapter{}
housingAdapter := &MockItemHousingAdapter{}
// Create loot manager adapter
lootAdapter := &WorldItemLootAdapter{}
// Create item system adapter
systemAdapter := items.NewItemSystemAdapter(
masterList,
nil, // SpellManager - will be set later when spells are integrated
playerAdapter,
packetAdapter,
ruleAdapter,
dbAdapter,
questAdapter,
brokerAdapter,
craftingAdapter,
housingAdapter,
lootAdapter,
)
return &ItemManager{
masterItemList: masterList,
itemSystemAdapter: systemAdapter,
database: db,
playerInventories: make(map[uint32]*items.PlayerItemList),
playerEquipment: make(map[uint32]*items.EquipmentItemList),
worldDrops: make(map[int32][]*items.Item),
}
}
// SetWorld sets the world server reference
func (im *ItemManager) SetWorld(world *World) {
im.world = world
}
// LoadItems loads all item templates and data from database
func (im *ItemManager) LoadItems() error {
fmt.Println("Loading item data...")
// Initialize the item system adapter
err := im.itemSystemAdapter.Initialize()
if err != nil {
return fmt.Errorf("failed to initialize item system: %w", err)
}
stats := im.masterItemList.GetStats()
fmt.Printf("Loaded %d item templates\n", stats.TotalItems)
return nil
}
// GetPlayerInventory gets a player's inventory
func (im *ItemManager) GetPlayerInventory(playerID uint32) (*items.PlayerItemList, error) {
im.mutex.RLock()
defer im.mutex.RUnlock()
return im.itemSystemAdapter.GetPlayerInventory(playerID)
}
// GetPlayerEquipment gets a player's equipment
func (im *ItemManager) GetPlayerEquipment(playerID uint32, appearanceType int8) (*items.EquipmentItemList, error) {
im.mutex.RLock()
defer im.mutex.RUnlock()
return im.itemSystemAdapter.GetPlayerEquipment(playerID, appearanceType)
}
// GiveItemToPlayer gives an item to a player
func (im *ItemManager) GiveItemToPlayer(playerID uint32, itemID int32, quantity int16) error {
return im.itemSystemAdapter.GiveItemToPlayer(playerID, itemID, quantity, items.NotSet)
}
// RemoveItemFromPlayer removes an item from a player
func (im *ItemManager) RemoveItemFromPlayer(playerID uint32, uniqueID int32, quantity int16) error {
return im.itemSystemAdapter.RemoveItemFromPlayer(playerID, uniqueID, quantity)
}
// EquipItem equips an item for a player
func (im *ItemManager) EquipItem(playerID uint32, uniqueID int32, slot int8) error {
return im.itemSystemAdapter.EquipItem(playerID, uniqueID, slot, 0) // Base equipment
}
// UnequipItem unequips an item for a player
func (im *ItemManager) UnequipItem(playerID uint32, slot int8) error {
return im.itemSystemAdapter.UnequipItem(playerID, slot, 0) // Base equipment
}
// MoveItem moves an item within a player's inventory
func (im *ItemManager) MoveItem(playerID uint32, fromBagID int32, fromSlot int16, toBagID int32, toSlot int16) error {
return im.itemSystemAdapter.MoveItem(playerID, fromBagID, fromSlot, toBagID, toSlot, 0) // Base equipment
}
// CreateWorldDrop creates an item drop in the world
func (im *ItemManager) CreateWorldDrop(itemID int32, quantity int16, x, y, z float32, zoneID int32) error {
im.mutex.Lock()
defer im.mutex.Unlock()
// Get item template
itemTemplate := im.masterItemList.GetItem(itemID)
if itemTemplate == nil {
return fmt.Errorf("item template not found: %d", itemID)
}
// Create item instance
item := items.NewItemFromTemplate(itemTemplate)
item.Details.Count = quantity
// Set position data (would normally be in a WorldItem wrapper)
// TODO: Create WorldItem wrapper with position data
// Add to world drops tracking
im.worldDrops[zoneID] = append(im.worldDrops[zoneID], item)
fmt.Printf("Created world drop: %s x%d at (%.2f, %.2f, %.2f) in zone %d\n",
item.Name, quantity, x, y, z, zoneID)
return nil
}
// GetWorldDrops gets all ground items in a zone
func (im *ItemManager) GetWorldDrops(zoneID int32) []*items.Item {
im.mutex.RLock()
defer im.mutex.RUnlock()
if drops, exists := im.worldDrops[zoneID]; exists {
// Return a copy to avoid concurrent modification
result := make([]*items.Item, len(drops))
copy(result, drops)
return result
}
return nil
}
// PickupWorldDrop handles picking up a ground item
func (im *ItemManager) PickupWorldDrop(playerID uint32, itemUniqueID int32, zoneID int32) error {
im.mutex.Lock()
defer im.mutex.Unlock()
// Find the item in world drops
drops, exists := im.worldDrops[zoneID]
if !exists {
return fmt.Errorf("no drops in zone %d", zoneID)
}
var foundItem *items.Item
var itemIndex int = -1
for i, item := range drops {
if int32(item.Details.UniqueID) == itemUniqueID {
foundItem = item
itemIndex = i
break
}
}
if foundItem == nil {
return fmt.Errorf("item not found: %d", itemUniqueID)
}
// Try to give item to player
err := im.GiveItemToPlayer(playerID, foundItem.Details.ItemID, foundItem.Details.Count)
if err != nil {
return fmt.Errorf("failed to give item to player: %w", err)
}
// Remove from world drops
im.worldDrops[zoneID] = append(drops[:itemIndex], drops[itemIndex+1:]...)
fmt.Printf("Player %d picked up item: %s x%d\n", playerID, foundItem.Name, foundItem.Details.Count)
return nil
}
// GenerateLootForNPC generates loot for an NPC kill
func (im *ItemManager) GenerateLootForNPC(npcID int32, killerLevel int16) ([]*items.Item, error) {
// TODO: Implement proper loot table lookup and generation
// For now, create some basic test loot
testLoot := []*items.Item{}
// Example: Generate some coins based on NPC level
// This would normally come from loot tables in the database
fmt.Printf("Generated loot for NPC %d (placeholder implementation)\n", npcID)
return testLoot, nil
}
// OnPlayerLogin handles player login - loads their item data
func (im *ItemManager) OnPlayerLogin(playerID uint32) error {
fmt.Printf("Loading item data for player %d...\n", playerID)
// Pre-load player inventory and equipment
_, err := im.GetPlayerInventory(playerID)
if err != nil {
return fmt.Errorf("failed to load player inventory: %w", err)
}
_, err = im.GetPlayerEquipment(playerID, items.BaseEquipment)
if err != nil {
return fmt.Errorf("failed to load player equipment: %w", err)
}
fmt.Printf("Item data loaded for player %d\n", playerID)
return nil
}
// OnPlayerLogout handles player logout - saves and clears their item data
func (im *ItemManager) OnPlayerLogout(playerID uint32) error {
fmt.Printf("Saving item data for player %d...\n", playerID)
// Save player data
err := im.itemSystemAdapter.SavePlayerData(playerID)
if err != nil {
return fmt.Errorf("failed to save player data: %w", err)
}
// Clear cached data
im.itemSystemAdapter.ClearPlayerData(playerID)
fmt.Printf("Item data saved and cleared for player %d\n", playerID)
return nil
}
// GetItemTemplate gets an item template by ID
func (im *ItemManager) GetItemTemplate(itemID int32) *items.Item {
return im.masterItemList.GetItem(itemID)
}
// SearchItems searches for item templates by name
func (im *ItemManager) SearchItems(name string, maxResults int32) []*items.Item {
// TODO: Implement proper search - for now return empty slice
return []*items.Item{}
}
// GetStatistics returns item system statistics
func (im *ItemManager) GetStatistics() map[string]interface{} {
im.mutex.RLock()
defer im.mutex.RUnlock()
systemStats := im.itemSystemAdapter.GetSystemStats()
// Add world-specific statistics
totalWorldDrops := 0
for _, drops := range im.worldDrops {
totalWorldDrops += len(drops)
}
result := make(map[string]interface{})
for k, v := range systemStats {
result[k] = v
}
result["world_drops"] = totalWorldDrops
result["zones_with_drops"] = len(im.worldDrops)
return result
}
// ValidatePlayerItems validates a player's items
func (im *ItemManager) ValidatePlayerItems(playerID uint32) *items.ItemValidationResult {
return im.itemSystemAdapter.ValidatePlayerItems(playerID)
}
// Shutdown gracefully shuts down the item manager
func (im *ItemManager) Shutdown() {
fmt.Println("Shutting down item manager...")
im.mutex.Lock()
defer im.mutex.Unlock()
// Clear all tracking
im.playerInventories = make(map[uint32]*items.PlayerItemList)
im.playerEquipment = make(map[uint32]*items.EquipmentItemList)
im.worldDrops = make(map[int32][]*items.Item)
fmt.Println("Item manager shutdown complete")
}
// WorldItemDatabaseAdapter adapts the world database for item use
type WorldItemDatabaseAdapter struct {
db *database.Database
}
// LoadItems implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) LoadItems(masterList *items.MasterItemList) error {
fmt.Println("Loading items from database...")
// Load item templates from database
rows, err := wdb.db.Query(`
SELECT id, name, item_type, icon, description, tier, level, classes, races,
adventure_classes, tradeskill_classes, stack_count, weight, generic_info,
bag_info, food_info, weapon_info, ranged_info, details, appearance
FROM items
ORDER BY id
`)
if err != nil {
return fmt.Errorf("failed to query items: %w", err)
}
defer rows.Close()
itemCount := 0
for rows.Next() {
// Create new item from database data
item := items.NewItem()
var id, itemType, icon, tier, level, stackCount int32
var weight, genericInfo, bagInfo, foodInfo, weaponInfo, rangedInfo int32
var details, appearance int32
var name, description, classes, races, adventureClasses, tradeskillClasses string
err := rows.Scan(&id, &name, &itemType, &icon, &description, &tier, &level,
&classes, &races, &adventureClasses, &tradeskillClasses, &stackCount,
&weight, &genericInfo, &bagInfo, &foodInfo, &weaponInfo, &rangedInfo,
&details, &appearance)
if err != nil {
fmt.Printf("Error scanning item row: %v\n", err)
continue
}
// Set item properties
item.Details.ItemID = id
item.Name = name
item.Details.Icon = int16(icon)
item.Description = description
item.Details.Tier = int8(tier)
item.Details.RecommendedLevel = int16(level)
item.StackCount = int16(stackCount)
// TODO: Set weight when field is available
// item.Weight = weight
// TODO: Parse class/race strings and set appropriate fields
// item.SetClasses(parseClassString(classes))
// item.SetRaces(parseRaceString(races))
// Add to master list
masterList.AddItem(item)
itemCount++
}
if err := rows.Err(); err != nil {
return fmt.Errorf("error iterating item rows: %w", err)
}
fmt.Printf("Successfully loaded %d item templates from database\n", itemCount)
return nil
}
// SaveItem implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) SaveItem(item *items.Item) error {
// TODO: Implement item template saving
fmt.Printf("Saving item template: %s (ID: %d) - not yet implemented\n", item.Name, item.Details.ItemID)
return nil
}
// DeleteItem implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) DeleteItem(itemID int32) error {
// TODO: Implement item template deletion
fmt.Printf("Deleting item template: %d - not yet implemented\n", itemID)
return nil
}
// LoadPlayerItems implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) LoadPlayerItems(playerID uint32) (*items.PlayerItemList, error) {
// TODO: Implement player inventory loading from database
fmt.Printf("Loading player inventory for player %d - creating empty inventory\n", playerID)
return items.NewPlayerItemList(), nil
}
// SavePlayerItems implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) SavePlayerItems(playerID uint32, itemList *items.PlayerItemList) error {
// TODO: Implement player inventory saving to database
fmt.Printf("Saving player inventory for player %d - not yet implemented\n", playerID)
return nil
}
// LoadPlayerEquipment implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) LoadPlayerEquipment(playerID uint32, appearanceType int8) (*items.EquipmentItemList, error) {
// TODO: Implement player equipment loading from database
fmt.Printf("Loading player equipment for player %d (type %d) - creating empty equipment\n", playerID, appearanceType)
equipment := items.NewEquipmentItemList()
equipment.SetAppearanceType(appearanceType)
return equipment, nil
}
// SavePlayerEquipment implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) SavePlayerEquipment(playerID uint32, equipment *items.EquipmentItemList) error {
// TODO: Implement player equipment saving to database
fmt.Printf("Saving player equipment for player %d - not yet implemented\n", playerID)
return nil
}
// LoadItemStats implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) LoadItemStats() (map[string]int32, map[int32]string, error) {
// TODO: Implement item stat mapping loading
fmt.Println("Loading item stats - using placeholder data")
statsStrings := map[string]int32{
"health": 1,
"power": 2,
"strength": 3,
"stamina": 4,
"agility": 5,
"wisdom": 6,
"intelligence": 7,
}
statsIDs := map[int32]string{
1: "health",
2: "power",
3: "strength",
4: "stamina",
5: "agility",
6: "wisdom",
7: "intelligence",
}
return statsStrings, statsIDs, nil
}
// SaveItemStat implements items.DatabaseService interface
func (wdb *WorldItemDatabaseAdapter) SaveItemStat(statID int32, statName string) error {
// TODO: Implement item stat mapping saving
fmt.Printf("Saving item stat mapping: %d = %s - not yet implemented\n", statID, statName)
return nil
}
// WorldItemPlayerAdapter adapts world player functionality for items
type WorldItemPlayerAdapter struct{}
// GetPlayer implements items.PlayerManager interface
func (wpa *WorldItemPlayerAdapter) GetPlayer(playerID uint32) (items.Player, error) {
// TODO: Get actual player from world server when player system is integrated
return &MockPlayer{id: playerID, name: fmt.Sprintf("Player_%d", playerID), level: 50}, nil
}
// GetPlayerLevel implements items.PlayerManager interface
func (wpa *WorldItemPlayerAdapter) GetPlayerLevel(playerID uint32) (int16, error) {
// TODO: Get actual player level from world server
return 50, nil // Placeholder
}
// GetPlayerClass implements items.PlayerManager interface
func (wpa *WorldItemPlayerAdapter) GetPlayerClass(playerID uint32) (int8, error) {
// TODO: Get actual player class from world server
return 1, nil // Placeholder
}
// GetPlayerRace implements items.PlayerManager interface
func (wpa *WorldItemPlayerAdapter) GetPlayerRace(playerID uint32) (int8, error) {
// TODO: Get actual player race from world server
return 1, nil // Placeholder
}
// SendMessageToPlayer implements items.PlayerManager interface
func (wpa *WorldItemPlayerAdapter) SendMessageToPlayer(playerID uint32, channel int8, message string) error {
// TODO: Send actual message via world server
fmt.Printf("[CHAT] Player %d: %s\n", playerID, message)
return nil
}
// GetPlayerName implements items.PlayerManager interface
func (wpa *WorldItemPlayerAdapter) GetPlayerName(playerID uint32) (string, error) {
// TODO: Get actual player name from world server
return fmt.Sprintf("Player_%d", playerID), nil
}
// WorldItemPacketAdapter adapts world packet functionality for items
type WorldItemPacketAdapter struct{}
// SendPacketToPlayer implements items.PacketManager interface
func (wpa *WorldItemPacketAdapter) SendPacketToPlayer(playerID uint32, packetData []byte) error {
// TODO: Send actual packet via world server
fmt.Printf("Sending item packet to player %d (%d bytes)\n", playerID, len(packetData))
return nil
}
// QueuePacketForPlayer implements items.PacketManager interface
func (wpa *WorldItemPacketAdapter) QueuePacketForPlayer(playerID uint32, packetData []byte) error {
// TODO: Queue packet via world server
fmt.Printf("Queuing item packet for player %d (%d bytes)\n", playerID, len(packetData))
return nil
}
// GetClientVersion implements items.PacketManager interface
func (wpa *WorldItemPacketAdapter) GetClientVersion(playerID uint32) (int16, error) {
// TODO: Get actual client version from world server
return 1096, nil // Default EQ2 client version
}
// SerializeItem implements items.PacketManager interface
func (wpa *WorldItemPacketAdapter) SerializeItem(item *items.Item, clientVersion int16, player items.Player) ([]byte, error) {
// TODO: Implement actual item serialization for network transmission
fmt.Printf("Serializing item %s for client version %d\n", item.Name, clientVersion)
return []byte{0x01, 0x02, 0x03}, nil // Placeholder data
}
// WorldItemRuleAdapter adapts world rules functionality for items
type WorldItemRuleAdapter struct{}
// GetBool implements items.RuleManager interface
func (wra *WorldItemRuleAdapter) GetBool(category, rule string) bool {
// TODO: Get actual rule values from world server rules system
fmt.Printf("Getting rule %s:%s (bool) - using default\n", category, rule)
return true // Default value
}
// GetInt32 implements items.RuleManager interface
func (wra *WorldItemRuleAdapter) GetInt32(category, rule string) int32 {
// TODO: Get actual rule values from world server rules system
fmt.Printf("Getting rule %s:%s (int32) - using default\n", category, rule)
return 100 // Default value
}
// GetFloat implements items.RuleManager interface
func (wra *WorldItemRuleAdapter) GetFloat(category, rule string) float32 {
// TODO: Get actual rule values from world server rules system
fmt.Printf("Getting rule %s:%s (float) - using default\n", category, rule)
return 1.0 // Default value
}
// GetString implements items.RuleManager interface
func (wra *WorldItemRuleAdapter) GetString(category, rule string) string {
// TODO: Get actual rule values from world server rules system
fmt.Printf("Getting rule %s:%s (string) - using default\n", category, rule)
return "default" // Default value
}
// WorldItemLootAdapter adapts world loot functionality for items
type WorldItemLootAdapter struct{}
// GenerateLoot implements items.LootManager interface
func (wla *WorldItemLootAdapter) GenerateLoot(lootTableID int32, playerLevel int16) ([]*items.Item, error) {
// TODO: Implement actual loot generation from loot tables
fmt.Printf("Generating loot from table %d for level %d player\n", lootTableID, playerLevel)
return []*items.Item{}, nil
}
// DistributeLoot implements items.LootManager interface
func (wla *WorldItemLootAdapter) DistributeLoot(lootItems []*items.Item, playerIDs []uint32, lootMethod int8) error {
// TODO: Implement actual loot distribution
fmt.Printf("Distributing %d items to %d players using method %d\n", len(lootItems), len(playerIDs), lootMethod)
return nil
}
// CanLootItem implements items.LootManager interface
func (wla *WorldItemLootAdapter) CanLootItem(playerID uint32, item *items.Item) bool {
// TODO: Implement actual loot permission checking
return true // Allow all looting for now
}
// Mock implementations for optional dependencies
// MockPlayer implements items.Player interface for testing
type MockPlayer struct {
id uint32
name string
level int16
adventureClass int8
tradeskillClass int8
race int8
gender int8
alignment int8
}
func (mp *MockPlayer) GetID() uint32 { return mp.id }
func (mp *MockPlayer) GetName() string { return mp.name }
func (mp *MockPlayer) GetLevel() int16 { return mp.level }
func (mp *MockPlayer) GetAdventureClass() int8 { return mp.adventureClass }
func (mp *MockPlayer) GetTradeskillClass() int8 { return mp.tradeskillClass }
func (mp *MockPlayer) GetRace() int8 { return mp.race }
func (mp *MockPlayer) GetGender() int8 { return mp.gender }
func (mp *MockPlayer) GetAlignment() int8 { return mp.alignment }
// MockItemQuestAdapter provides mock quest functionality
type MockItemQuestAdapter struct{}
func (mqa *MockItemQuestAdapter) CheckQuestPrerequisites(playerID uint32, questID int32) bool {
return true // Allow all for now
}
func (mqa *MockItemQuestAdapter) GetQuestRewards(questID int32) ([]*items.QuestRewardData, error) {
return []*items.QuestRewardData{}, nil
}
func (mqa *MockItemQuestAdapter) IsQuestItem(itemID int32) bool {
return false // No quest items for now
}
// MockItemBrokerAdapter provides mock broker functionality
type MockItemBrokerAdapter struct{}
func (mba *MockItemBrokerAdapter) SearchItems(criteria *items.ItemSearchCriteria) ([]*items.Item, error) {
return []*items.Item{}, nil
}
func (mba *MockItemBrokerAdapter) ListItem(playerID uint32, item *items.Item, price int64) error {
return fmt.Errorf("broker not implemented")
}
func (mba *MockItemBrokerAdapter) BuyItem(playerID uint32, itemID int32, sellerID uint32) error {
return fmt.Errorf("broker not implemented")
}
func (mba *MockItemBrokerAdapter) GetItemPrice(itemID int32) (int64, error) {
return 0, fmt.Errorf("broker not implemented")
}
// MockItemCraftingAdapter provides mock crafting functionality
type MockItemCraftingAdapter struct{}
func (mca *MockItemCraftingAdapter) CanCraftItem(playerID uint32, itemID int32) bool {
return false // No crafting for now
}
func (mca *MockItemCraftingAdapter) GetCraftingRequirements(itemID int32) ([]items.CraftingRequirement, error) {
return []items.CraftingRequirement{}, nil
}
func (mca *MockItemCraftingAdapter) CraftItem(playerID uint32, itemID int32, quality int8) (*items.Item, error) {
return nil, fmt.Errorf("crafting not implemented")
}
// MockItemHousingAdapter provides mock housing functionality
type MockItemHousingAdapter struct{}
func (mha *MockItemHousingAdapter) CanPlaceItem(playerID uint32, houseID int32, item *items.Item) bool {
return false // No housing for now
}
func (mha *MockItemHousingAdapter) PlaceItem(playerID uint32, houseID int32, item *items.Item, location items.HouseLocation) error {
return fmt.Errorf("housing not implemented")
}
func (mha *MockItemHousingAdapter) RemoveItem(playerID uint32, houseID int32, itemID int32) error {
return fmt.Errorf("housing not implemented")
}
func (mha *MockItemHousingAdapter) GetHouseItems(houseID int32) ([]*items.Item, error) {
return []*items.Item{}, nil
}

View File

@ -273,6 +273,35 @@ func (nm *NPCManager) OnNPCKilled(npcID int32, killerCharacterID int32) {
}
}
// Generate and distribute loot
if nm.world != nil && nm.world.itemMgr != nil {
npcInfo := nm.GetNPCInfo(npcID)
if npcInfo != nil {
// Generate loot for this NPC kill
loot, err := nm.world.itemMgr.GenerateLootForNPC(npcID, int16(npcInfo.Level))
if err != nil {
fmt.Printf("Failed to generate loot for NPC %d: %v\n", npcID, err)
} else if len(loot) > 0 {
// Create loot drops in the world (for now, just create basic drops)
// TODO: Get NPC position for proper loot positioning
x, y, z := float32(100), float32(100), float32(50)
zoneID := npcInfo.ZoneID
if zoneID == 0 {
zoneID = 1 // Default zone
}
for _, item := range loot {
err := nm.world.itemMgr.CreateWorldDrop(item.Details.ItemID, item.Details.Count, x, y, z, zoneID)
if err != nil {
fmt.Printf("Failed to create loot drop for item %d: %v\n", item.Details.ItemID, err)
}
}
fmt.Printf("Generated %d loot items for NPC %d kill\n", len(loot), npcID)
}
}
}
fmt.Printf("NPC %d killed by character %d\n", npcID, killerCharacterID)
}

View File

@ -49,7 +49,16 @@ func (w *World) RegisterPacketHandlers() {
packets.RegisterGlobalHandler(packets.OP_NPCSpellCastMsg, w.HandleNPCSpellCast)
packets.RegisterGlobalHandler(packets.OP_NPCMovementMsg, w.HandleNPCMovement)
fmt.Printf("Registered %d packet handlers\n", 21)
// Item system
packets.RegisterGlobalHandler(packets.OP_ItemMoveMsg, w.HandleItemMove)
packets.RegisterGlobalHandler(packets.OP_ItemEquipMsg, w.HandleItemEquip)
packets.RegisterGlobalHandler(packets.OP_ItemUnequipMsg, w.HandleItemUnequip)
packets.RegisterGlobalHandler(packets.OP_ItemPickupMsg, w.HandleItemPickup)
packets.RegisterGlobalHandler(packets.OP_ItemDropMsg, w.HandleItemDrop)
packets.RegisterGlobalHandler(packets.OP_ItemExamineMsg, w.HandleItemExamine)
packets.RegisterGlobalHandler(packets.OP_ItemUpdateMsg, w.HandleItemUpdate)
fmt.Printf("Registered %d packet handlers\n", 28)
}
// HandleDoneLoadingZoneResources handles when client finishes loading zone resources
@ -702,4 +711,268 @@ func (w *World) CreatePacketContext(client *Client) *packets.PacketContext {
World: &WorldServerAdapter{world: w},
Database: &WorldDatabaseAdapter{world: w},
}
}
// Item Packet Handlers
// HandleItemMove handles item movement within player inventory
func (w *World) HandleItemMove(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent item move packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse move data from packet (fromBagID, fromSlot, toBagID, toSlot)
// For now, use placeholder values for testing
fromBagID := int32(0)
fromSlot := int16(0)
toBagID := int32(0)
toSlot := int16(1)
if w.itemMgr != nil {
err := w.itemMgr.MoveItem(uint32(ctx.Client.GetCharacterID()),
fromBagID, fromSlot, toBagID, toSlot)
if err != nil {
client.SendSimpleMessage(fmt.Sprintf("Item move failed: %v", err))
} else {
client.SendSimpleMessage("Item moved successfully")
}
}
}
return nil
}
// HandleItemEquip handles item equipping
func (w *World) HandleItemEquip(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent item equip packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse equip data from packet (uniqueID, slot)
// For now, use placeholder values for testing
uniqueID := int32(1001)
slot := int8(0) // Primary hand
if w.itemMgr != nil {
err := w.itemMgr.EquipItem(uint32(ctx.Client.GetCharacterID()), uniqueID, slot)
if err != nil {
client.SendSimpleMessage(fmt.Sprintf("Item equip failed: %v", err))
} else {
client.SendSimpleMessage(fmt.Sprintf("Item equipped to slot %d", slot))
}
}
}
return nil
}
// HandleItemUnequip handles item unequipping
func (w *World) HandleItemUnequip(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent item unequip packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse unequip data from packet (slot)
// For now, use placeholder values for testing
slot := int8(0) // Primary hand
if w.itemMgr != nil {
err := w.itemMgr.UnequipItem(uint32(ctx.Client.GetCharacterID()), slot)
if err != nil {
client.SendSimpleMessage(fmt.Sprintf("Item unequip failed: %v", err))
} else {
client.SendSimpleMessage(fmt.Sprintf("Item unequipped from slot %d", slot))
}
}
}
return nil
}
// HandleItemPickup handles picking up world drops
func (w *World) HandleItemPickup(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent item pickup packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse pickup data from packet (itemUniqueID)
// For now, use placeholder values for testing
itemUniqueID := int32(5001)
zoneID := int32(1) // Current zone
if w.itemMgr != nil {
err := w.itemMgr.PickupWorldDrop(uint32(ctx.Client.GetCharacterID()), itemUniqueID, zoneID)
if err != nil {
client.SendSimpleMessage(fmt.Sprintf("Item pickup failed: %v", err))
} else {
client.SendSimpleMessage("Item picked up")
// TODO: Remove item from world display for other players
}
}
}
return nil
}
// HandleItemDrop handles dropping items to the world
func (w *World) HandleItemDrop(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent item drop packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse drop data from packet (uniqueID, quantity, x, y, z)
// For now, use placeholder values for testing
itemID := int32(1001)
quantity := int16(1)
x, y, z := float32(100), float32(100), float32(50)
zoneID := int32(1)
if w.itemMgr != nil {
// First remove from player inventory (would need to look up by uniqueID)
// TODO: Get uniqueID from packet and remove from player
// Create world drop
err := w.itemMgr.CreateWorldDrop(itemID, quantity, x, y, z, zoneID)
if err != nil {
client.SendSimpleMessage(fmt.Sprintf("Item drop failed: %v", err))
} else {
client.SendSimpleMessage("Item dropped")
// TODO: Show item to other players in range
}
}
}
return nil
}
// HandleItemExamine handles item examination requests
func (w *World) HandleItemExamine(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent item examine packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse examine data from packet (uniqueID or itemID)
// For now, use placeholder values for testing
itemID := int32(1001)
if w.itemMgr != nil {
itemTemplate := w.itemMgr.GetItemTemplate(itemID)
if itemTemplate == nil {
client.SendSimpleMessage("Item not found")
} else {
// Send item details to client
w.SendItemDetails(client, itemTemplate)
}
}
}
return nil
}
// HandleItemUpdate handles item update notifications
func (w *World) HandleItemUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent item update packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse update data from packet
// This might be triggered when client needs updated item information
if w.itemMgr != nil {
// Send updated inventory to client
w.SendPlayerInventory(client)
}
}
return nil
}
// SendItemDetails sends detailed item information to a client
func (w *World) SendItemDetails(client *Client, item interface{}) {
if w.itemMgr == nil {
return
}
// TODO: Implement actual item detail packet building
// This would include stats, description, level requirements, etc.
fmt.Printf("Sending item details to %s\n", client.CharacterName)
// Placeholder - send basic item info as chat message
client.SendSimpleMessage("Item Details: [Item information would be displayed here]")
}
// SendPlayerInventory sends complete inventory to a client
func (w *World) SendPlayerInventory(client *Client) {
if w.itemMgr == nil {
return
}
playerID := uint32(client.CharacterID)
// Get player inventory and equipment
inventory, err := w.itemMgr.GetPlayerInventory(playerID)
if err != nil {
fmt.Printf("Failed to get inventory for player %d: %v\n", playerID, err)
return
}
equipment, err := w.itemMgr.GetPlayerEquipment(playerID, 0) // Base equipment
if err != nil {
fmt.Printf("Failed to get equipment for player %d: %v\n", playerID, err)
return
}
// TODO: Build and send inventory packet
fmt.Printf("Sending inventory to %s: %d inventory items, %d equipped items\n",
client.CharacterName, inventory.GetNumberOfItems(), equipment.GetNumberOfItems())
// Placeholder - send summary as chat message
client.SendSimpleMessage(fmt.Sprintf("Inventory Update: %d items in inventory, %d equipped",
inventory.GetNumberOfItems(), equipment.GetNumberOfItems()))
}
// SendItemUpdate sends item update to client
func (w *World) SendItemUpdate(client *Client, updateType string, itemData map[string]interface{}) {
if w.itemMgr == nil {
return
}
// TODO: Build and send item update packet
fmt.Printf("Sending item update to %s: %s - %v\n",
client.CharacterName, updateType, itemData)
// Placeholder - send update as chat message
client.SendSimpleMessage(fmt.Sprintf("Item Update: %s", updateType))
}
// BroadcastItemUpdate broadcasts item updates to nearby players
func (w *World) BroadcastItemUpdate(sourcePlayerID uint32, updateType string, itemData map[string]interface{}) {
// TODO: Implement item update broadcasting (for things like equipment changes visible to others)
fmt.Printf("Broadcasting item update from player %d: %s - %v\n",
sourcePlayerID, updateType, itemData)
// Send to players in range (placeholder)
clients := w.clients.GetAll()
for _, client := range clients {
if client.CurrentZone != nil && client.CharacterID != int32(sourcePlayerID) {
// TODO: Check if client is in range
client.SendSimpleMessage(fmt.Sprintf("Player item update: %s", updateType))
}
}
}

View File

@ -44,9 +44,11 @@ type World struct {
// NPC system
npcMgr *NPCManager
// Item system
itemMgr *ItemManager
// Master lists (singletons)
masterSpells interface{} // TODO: implement spell manager
masterItems interface{} // TODO: implement item manager
masterQuests interface{} // TODO: implement quest manager
masterSkills interface{} // TODO: implement skill manager
masterFactions interface{} // TODO: implement faction manager
@ -159,6 +161,9 @@ func NewWorld(config *WorldConfig) (*World, error) {
// Initialize NPC manager
npcMgr := NewNPCManager(db)
// Initialize item manager
itemMgr := NewItemManager(db)
// Create context
ctx, cancel := context.WithCancel(context.Background())
@ -169,6 +174,7 @@ func NewWorld(config *WorldConfig) (*World, error) {
achievementMgr: achievementMgr,
titleMgr: titleMgr,
npcMgr: npcMgr,
itemMgr: itemMgr,
config: config,
startTime: time.Now(),
worldTime: &WorldTime{Year: 3721, Month: 1, Day: 1, Hour: 12, Minute: 0},
@ -184,6 +190,7 @@ func NewWorld(config *WorldConfig) (*World, error) {
// Set world references for cross-system communication
achievementMgr.SetWorld(w)
npcMgr.SetWorld(w)
itemMgr.SetWorld(w)
// Load server data from database
if err := w.loadServerData(); err != nil {
@ -436,6 +443,12 @@ func (w *World) loadServerData() error {
// Don't fail startup if NPCs don't load - server can still run
}
// Load items
if err := w.itemMgr.LoadItems(); err != nil {
fmt.Printf("Warning: Failed to load items: %v\n", err)
// Don't fail startup if items don't load - server can still run
}
// Setup title and achievement integration
w.setupTitleAchievementIntegration()
@ -605,6 +618,13 @@ func (w *World) loadSampleOpcodeMappings() {
"OP_NPCInfoMsg": 0x0097,
"OP_NPCSpellCastMsg": 0x0098,
"OP_NPCMovementMsg": 0x0099,
"OP_ItemMoveMsg": 0x00A0,
"OP_ItemEquipMsg": 0x00A1,
"OP_ItemUnequipMsg": 0x00A2,
"OP_ItemPickupMsg": 0x00A3,
"OP_ItemDropMsg": 0x00A4,
"OP_ItemExamineMsg": 0x00A5,
"OP_ItemUpdateMsg": 0x00A6,
"OP_EqHearChatCmd": 0x1000,
"OP_EqDisplayTextCmd": 0x1001,
"OP_EqCreateGhostCmd": 0x1002,