package trade import ( "fmt" "sync" "time" ) // Trade represents an active trade between two entities // Converted from C++ Trade class type Trade struct { // Core trade participants trader1 *TradeParticipant // First trader (initiator) trader2 *TradeParticipant // Second trader (recipient) // Trade state state TradeState // Current state of the trade startTime time.Time // When the trade was initiated // Thread safety mutex sync.RWMutex // TODO: Add references to packet system and entity manager when available // packetManager *PacketManager // entityManager *EntityManager } // NewTrade creates a new trade between two entities // Converted from C++ Trade::Trade constructor func NewTrade(entity1, entity2 Entity) *Trade { if entity1 == nil || entity2 == nil { return nil } trade := &Trade{ trader1: NewTradeParticipant(entity1.GetID(), entity1.IsBot(), entity1.GetClientVersion()), trader2: NewTradeParticipant(entity2.GetID(), entity2.IsBot(), entity2.GetClientVersion()), state: TradeStateActive, startTime: time.Now(), } // TODO: Open trade window when packet system is available // trade.openTradeWindow() return trade } // GetTrader1ID returns the ID of the first trader func (t *Trade) GetTrader1ID() int32 { t.mutex.RLock() defer t.mutex.RUnlock() return t.trader1.EntityID } // GetTrader2ID returns the ID of the second trader func (t *Trade) GetTrader2ID() int32 { t.mutex.RLock() defer t.mutex.RUnlock() return t.trader2.EntityID } // GetTradee returns the other participant in the trade // Converted from C++ Trade::GetTradee func (t *Trade) GetTradee(entityID int32) int32 { t.mutex.RLock() defer t.mutex.RUnlock() if t.trader1.EntityID == entityID { return t.trader2.EntityID } else if t.trader2.EntityID == entityID { return t.trader1.EntityID } return 0 // Invalid entity ID } // GetParticipant returns the trade participant for an entity func (t *Trade) GetParticipant(entityID int32) *TradeParticipant { t.mutex.RLock() defer t.mutex.RUnlock() if t.trader1.EntityID == entityID { return t.trader1 } else if t.trader2.EntityID == entityID { return t.trader2 } return nil } // GetState returns the current trade state func (t *Trade) GetState() TradeState { t.mutex.RLock() defer t.mutex.RUnlock() return t.state } // AddItemToTrade adds an item to a participant's trade offer // Converted from C++ Trade::AddItemToTrade func (t *Trade) AddItemToTrade(entityID int32, item Item, quantity int32, slot int8) error { if t.state != TradeStateActive { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Trade is not active", } } t.mutex.Lock() defer t.mutex.Unlock() participant := t.getParticipantUnsafe(entityID) if participant == nil { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Entity is not part of this trade", } } // Auto-find slot if needed if slot == TradeSlotAutoFind { slot = participant.GetNextFreeSlot() } // Validate slot if slot < 0 || slot >= participant.MaxSlots { return &TradeValidationError{ Code: TradeResultSlotOutOfRange, Message: fmt.Sprintf("Invalid trade slot: %d", slot), } } // Check if slot is already occupied if _, exists := participant.Items[slot]; exists { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Trade slot is already occupied", } } // Validate quantity if quantity <= 0 { quantity = 1 } if quantity > item.GetQuantity() { return &TradeValidationError{ Code: TradeResultInsufficientQty, Message: "Not enough quantity available", } } // Check if item is already in trade if participant.HasItem(item.GetID()) { return &TradeValidationError{ Code: TradeResultAlreadyInTrade, Message: "Item is already in trade", } } // Validate item tradability otherID := t.getTradeeUnsafe(entityID) if err := t.validateItemTradability(item, entityID, otherID); err != nil { return err } // Add item to trade participant.Items[slot] = TradeItemInfo{ Item: item, Quantity: quantity, } // Reset acceptance flags t.trader1.HasAccepted = false t.trader2.HasAccepted = false // TODO: Send trade packet when packet system is available // t.sendTradePacket() return nil } // RemoveItemFromTrade removes an item from a participant's trade offer // Converted from C++ Trade::RemoveItemFromTrade func (t *Trade) RemoveItemFromTrade(entityID int32, slot int8) error { if t.state != TradeStateActive { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Trade is not active", } } t.mutex.Lock() defer t.mutex.Unlock() participant := t.getParticipantUnsafe(entityID) if participant == nil { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Entity is not part of this trade", } } // Check if slot has an item if _, exists := participant.Items[slot]; !exists { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Trade slot is empty", } } // Remove item delete(participant.Items, slot) // Reset acceptance flags t.trader1.HasAccepted = false t.trader2.HasAccepted = false // TODO: Send trade packet when packet system is available // t.sendTradePacket() return nil } // AddCoinsToTrade adds coins to a participant's trade offer // Converted from C++ Trade::AddCoinToTrade func (t *Trade) AddCoinsToTrade(entityID int32, amount int64) error { if t.state != TradeStateActive { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Trade is not active", } } if amount <= 0 { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Invalid coin amount", } } t.mutex.Lock() defer t.mutex.Unlock() participant := t.getParticipantUnsafe(entityID) if participant == nil { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Entity is not part of this trade", } } newTotal := participant.Coins + amount // TODO: Validate entity has sufficient coins when entity system is available // For now, assume validation is done elsewhere participant.Coins = newTotal // Reset acceptance flags t.trader1.HasAccepted = false t.trader2.HasAccepted = false // TODO: Send trade packet when packet system is available // t.sendTradePacket() return nil } // RemoveCoinsFromTrade removes coins from a participant's trade offer // Converted from C++ Trade::RemoveCoinFromTrade func (t *Trade) RemoveCoinsFromTrade(entityID int32, amount int64) error { if t.state != TradeStateActive { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Trade is not active", } } t.mutex.Lock() defer t.mutex.Unlock() participant := t.getParticipantUnsafe(entityID) if participant == nil { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Entity is not part of this trade", } } if amount >= participant.Coins { participant.Coins = 0 } else { participant.Coins -= amount } // Reset acceptance flags t.trader1.HasAccepted = false t.trader2.HasAccepted = false // TODO: Send trade packet when packet system is available // t.sendTradePacket() return nil } // SetTradeAccepted marks a participant as having accepted the trade // Converted from C++ Trade::SetTradeAccepted func (t *Trade) SetTradeAccepted(entityID int32) (bool, error) { if t.state != TradeStateActive { return false, &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Trade is not active", } } t.mutex.Lock() defer t.mutex.Unlock() participant := t.getParticipantUnsafe(entityID) if participant == nil { return false, &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Entity is not part of this trade", } } participant.HasAccepted = true // Check if both parties have accepted if t.trader1.HasAccepted && t.trader2.HasAccepted { // Complete the trade t.completeTrade() return true, nil } // TODO: Send acceptance packet to other trader when packet system is available return false, nil } // HasAcceptedTrade checks if a participant has accepted the trade // Converted from C++ Trade::HasAcceptedTrade func (t *Trade) HasAcceptedTrade(entityID int32) bool { t.mutex.RLock() defer t.mutex.RUnlock() participant := t.getParticipantUnsafe(entityID) if participant == nil { return false } return participant.HasAccepted } // CancelTrade cancels the trade // Converted from C++ Trade::CancelTrade func (t *Trade) CancelTrade(entityID int32) error { t.mutex.Lock() defer t.mutex.Unlock() if t.state != TradeStateActive { return &TradeValidationError{ Code: TradeResultInvalidSlot, Message: "Trade is not active", } } t.state = TradeStateCanceled // TODO: Send cancel packets to both traders when packet system is available // TODO: Clear trade references on entities when entity system is available return nil } // GetTraderSlot returns the item in a specific slot for a trader // Converted from C++ Trade::GetTraderSlot func (t *Trade) GetTraderSlot(entityID int32, slot int8) Item { t.mutex.RLock() defer t.mutex.RUnlock() participant := t.getParticipantUnsafe(entityID) if participant == nil { return nil } if itemInfo, exists := participant.Items[slot]; exists { return itemInfo.Item } return nil } // GetTradeInfo returns comprehensive information about the trade func (t *Trade) GetTradeInfo() map[string]interface{} { t.mutex.RLock() defer t.mutex.RUnlock() info := make(map[string]interface{}) info["state"] = t.state info["start_time"] = t.startTime info["trader1_id"] = t.trader1.EntityID info["trader2_id"] = t.trader2.EntityID info["trader1_accepted"] = t.trader1.HasAccepted info["trader2_accepted"] = t.trader2.HasAccepted info["trader1_items"] = len(t.trader1.Items) info["trader2_items"] = len(t.trader2.Items) info["trader1_coins"] = t.trader1.Coins info["trader2_coins"] = t.trader2.Coins return info } // Private helper methods // getParticipantUnsafe returns participant without locking (must be called with lock held) func (t *Trade) getParticipantUnsafe(entityID int32) *TradeParticipant { if t.trader1.EntityID == entityID { return t.trader1 } else if t.trader2.EntityID == entityID { return t.trader2 } return nil } // getTradeeUnsafe returns the other trader's ID without locking func (t *Trade) getTradeeUnsafe(entityID int32) int32 { if t.trader1.EntityID == entityID { return t.trader2.EntityID } else if t.trader2.EntityID == entityID { return t.trader1.EntityID } return 0 } // validateItemTradability checks if an item can be traded between entities // Converted from C++ Trade::CheckItem func (t *Trade) validateItemTradability(item Item, traderID, otherID int32) error { // Check no-trade flag if item.IsNoTrade() { // TODO: Only allow no-trade items with bots when bot system is available return &TradeValidationError{ Code: TradeResultNoTrade, Message: "Item cannot be traded", } } // Check heirloom restrictions if item.IsHeirloom() { // TODO: Implement heirloom group checking when group system is available // For now, allow heirloom trades with basic time/attunement checks if item.IsAttuned() { return &TradeValidationError{ Code: TradeResultHeirloom, Message: "Attuned heirloom items cannot be traded", } } // Check time-based restrictions (48 hours default) creationTime := item.GetCreationTime() if time.Since(creationTime) > 48*time.Hour { return &TradeValidationError{ Code: TradeResultHeirloom, Message: "Heirloom item sharing period has expired", } } } return nil } // completeTrade finalizes the trade and transfers items/coins // Converted from C++ Trade::CompleteTrade func (t *Trade) completeTrade() { t.state = TradeStateCompleted // TODO: Implement actual item/coin transfer when entity and inventory systems are available // This would involve: // 1. Remove items/coins from each trader's inventory // 2. Add the other trader's items/coins to their inventory // 3. Send completion packets // 4. Log the trade // 5. Clear trade references on entities // For now, just mark as completed }