379 lines
9.9 KiB
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]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
|
|
}
|