488 lines
12 KiB
Go

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]any {
t.mutex.RLock()
defer t.mutex.RUnlock()
info := make(map[string]any)
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
}