package trade import ( "fmt" "sync" "time" ) // TradeService provides high-level trade management functionality // This integrates the trade system with the broader server architecture type TradeService struct { tradeManager *TradeManager // Trade configuration maxTradeDuration time.Duration // Maximum time a trade can be active // TODO: Add references to other systems when available // entityManager *EntityManager // packetManager *PacketManager // logManager *LogManager mutex sync.RWMutex } // NewTradeService creates a new trade service func NewTradeService() *TradeService { return &TradeService{ tradeManager: NewTradeManager(), maxTradeDuration: 30 * time.Minute, // Default 30 minute timeout } } // InitiateTrade starts a trade between two entities // This is the main entry point for starting trades func (ts *TradeService) InitiateTrade(initiatorID, targetID int32) (*Trade, error) { ts.mutex.Lock() defer ts.mutex.Unlock() // Check if either entity is already in a trade if existingTrade := ts.tradeManager.GetTrade(initiatorID); existingTrade != nil { return nil, fmt.Errorf("initiator is already in a trade") } if existingTrade := ts.tradeManager.GetTrade(targetID); existingTrade != nil { return nil, fmt.Errorf("target is already in a trade") } // TODO: Get actual entities when entity system is available // For now, create placeholder entities initiator := &PlaceholderEntity{ID: initiatorID} target := &PlaceholderEntity{ID: targetID} // Create new trade trade := NewTrade(initiator, target) if trade == nil { return nil, fmt.Errorf("failed to create trade") } // Add to trade manager ts.tradeManager.AddTrade(trade) return trade, nil } // GetTrade retrieves an active trade for an entity func (ts *TradeService) GetTrade(entityID int32) *Trade { return ts.tradeManager.GetTrade(entityID) } // AddItemToTrade adds an item to a player's trade offer func (ts *TradeService) AddItemToTrade(entityID int32, item Item, quantity int32, slot int8) error { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return fmt.Errorf("entity is not in a trade") } return trade.AddItemToTrade(entityID, item, quantity, slot) } // RemoveItemFromTrade removes an item from a player's trade offer func (ts *TradeService) RemoveItemFromTrade(entityID int32, slot int8) error { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return fmt.Errorf("entity is not in a trade") } return trade.RemoveItemFromTrade(entityID, slot) } // AddCoinsToTrade adds coins to a player's trade offer func (ts *TradeService) AddCoinsToTrade(entityID int32, amount int64) error { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return fmt.Errorf("entity is not in a trade") } return trade.AddCoinsToTrade(entityID, amount) } // RemoveCoinsFromTrade removes coins from a player's trade offer func (ts *TradeService) RemoveCoinsFromTrade(entityID int32, amount int64) error { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return fmt.Errorf("entity is not in a trade") } return trade.RemoveCoinsFromTrade(entityID, amount) } // AcceptTrade marks a player as having accepted their trade func (ts *TradeService) AcceptTrade(entityID int32) (bool, error) { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return false, fmt.Errorf("entity is not in a trade") } completed, err := trade.SetTradeAccepted(entityID) if err != nil { return false, err } // If trade completed, remove from manager if completed { ts.tradeManager.RemoveTrade(trade.GetTrader1ID()) } return completed, nil } // CancelTrade cancels an active trade func (ts *TradeService) CancelTrade(entityID int32) error { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return fmt.Errorf("entity is not in a trade") } err := trade.CancelTrade(entityID) if err != nil { return err } // Remove from manager ts.tradeManager.RemoveTrade(trade.GetTrader1ID()) return nil } // GetTradeInfo returns comprehensive information about a trade func (ts *TradeService) GetTradeInfo(entityID int32) (map[string]any, error) { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return nil, fmt.Errorf("entity is not in a trade") } return trade.GetTradeInfo(), nil } // GetActiveTradeCount returns the number of active trades func (ts *TradeService) GetActiveTradeCount() int { return ts.tradeManager.GetActiveTradeCount() } // ProcessTrades handles periodic trade processing (timeouts, cleanup, etc.) func (ts *TradeService) ProcessTrades() { ts.mutex.Lock() defer ts.mutex.Unlock() // TODO: Implement trade timeout processing // This would check for trades that have been active too long and auto-cancel them // Get all active trades // Check each trade's start time against maxTradeDuration // Cancel expired trades } // GetTradeStatistics returns statistics about trade activity func (ts *TradeService) GetTradeStatistics() map[string]any { stats := make(map[string]any) stats["active_trades"] = ts.tradeManager.GetActiveTradeCount() stats["max_trade_duration_minutes"] = ts.maxTradeDuration.Minutes() // TODO: Add more statistics when logging/metrics system is available // - Total trades completed today // - Average trade completion time // - Most traded items // - Trade success/failure rates return stats } // ValidateTradeRequest checks if a trade request is valid func (ts *TradeService) ValidateTradeRequest(initiatorID, targetID int32) error { if initiatorID == targetID { return fmt.Errorf("cannot trade with yourself") } if initiatorID <= 0 || targetID <= 0 { return fmt.Errorf("invalid entity IDs") } // Check if either entity is already in a trade if ts.tradeManager.GetTrade(initiatorID) != nil { return fmt.Errorf("initiator is already in a trade") } if ts.tradeManager.GetTrade(targetID) != nil { return fmt.Errorf("target is already in a trade") } // TODO: Add additional validation when entity system is available: // - Verify both entities exist and are online // - Check if entities are in the same zone // - Verify entities are within trade range // - Check for any trade restrictions or bans return nil } // ForceCompleteTrade completes a trade regardless of acceptance state (admin function) func (ts *TradeService) ForceCompleteTrade(entityID int32) error { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return fmt.Errorf("entity is not in a trade") } // Force both participants to accepted state trade.trader1.HasAccepted = true trade.trader2.HasAccepted = true // Complete the trade completed, err := trade.SetTradeAccepted(entityID) if err != nil { return err } if completed { ts.tradeManager.RemoveTrade(trade.GetTrader1ID()) } return nil } // ForceCancelTrade cancels a trade regardless of state (admin function) func (ts *TradeService) ForceCancelTrade(entityID int32, reason string) error { trade := ts.tradeManager.GetTrade(entityID) if trade == nil { return fmt.Errorf("entity is not in a trade") } // TODO: Log the forced cancellation with reason when logging system is available err := trade.CancelTrade(entityID) if err != nil { return err } ts.tradeManager.RemoveTrade(trade.GetTrader1ID()) return nil } // Shutdown gracefully shuts down the trade service func (ts *TradeService) Shutdown() { ts.mutex.Lock() defer ts.mutex.Unlock() // TODO: Cancel all active trades with appropriate notifications // For now, just clear the trade manager ts.tradeManager = NewTradeManager() } // PlaceholderEntity is a temporary implementation until the entity system is available type PlaceholderEntity struct { ID int32 Name string IsPlayerFlag bool IsBotFlag bool CoinsAmount int64 ClientVer int32 } // GetID returns the entity ID func (pe *PlaceholderEntity) GetID() int32 { return pe.ID } // GetName returns the entity name func (pe *PlaceholderEntity) GetName() string { if pe.Name == "" { return fmt.Sprintf("Entity_%d", pe.ID) } return pe.Name } // IsPlayer returns whether this is a player entity func (pe *PlaceholderEntity) IsPlayer() bool { return pe.IsPlayerFlag } // IsBot returns whether this is a bot entity func (pe *PlaceholderEntity) IsBot() bool { return pe.IsBotFlag } // HasCoins checks if the entity has sufficient coins func (pe *PlaceholderEntity) HasCoins(amount int64) bool { return pe.CoinsAmount >= amount } // GetClientVersion returns the client version func (pe *PlaceholderEntity) GetClientVersion() int32 { if pe.ClientVer == 0 { return 1000 // Default to newer client } return pe.ClientVer } // PlaceholderItem is a temporary implementation until the item system is available type PlaceholderItem struct { ID int32 Name string Quantity int32 IconID int32 NoTradeFlag bool HeirloomFlag bool AttunedFlag bool CreatedTime time.Time GroupIDs []int32 } // GetID returns the item ID func (pi *PlaceholderItem) GetID() int32 { return pi.ID } // GetName returns the item name func (pi *PlaceholderItem) GetName() string { if pi.Name == "" { return fmt.Sprintf("Item_%d", pi.ID) } return pi.Name } // GetQuantity returns the item quantity func (pi *PlaceholderItem) GetQuantity() int32 { return pi.Quantity } // GetIcon returns the item icon ID func (pi *PlaceholderItem) GetIcon(version int32) int32 { return pi.IconID } // IsNoTrade returns whether the item is no-trade func (pi *PlaceholderItem) IsNoTrade() bool { return pi.NoTradeFlag } // IsHeirloom returns whether the item is heirloom func (pi *PlaceholderItem) IsHeirloom() bool { return pi.HeirloomFlag } // IsAttuned returns whether the item is attuned func (pi *PlaceholderItem) IsAttuned() bool { return pi.AttunedFlag } // GetCreationTime returns when the item was created func (pi *PlaceholderItem) GetCreationTime() time.Time { return pi.CreatedTime } // GetGroupCharacterIDs returns the group character IDs for heirloom sharing func (pi *PlaceholderItem) GetGroupCharacterIDs() []int32 { return pi.GroupIDs }