eq2go/internal/trade/manager.go

379 lines
9.9 KiB
Go

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]interface{}, 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]interface{} {
stats := make(map[string]interface{})
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
}