eq2go/internal/trade/utils.go

207 lines
6.0 KiB
Go

package trade
import (
"fmt"
"strings"
)
// CalculateCoins converts a total copper amount to coin denominations
// Converted from C++ Trade::CalculateCoins
func CalculateCoins(totalCopper int64) CoinAmounts {
coins := CoinAmounts{}
remaining := totalCopper
// Calculate platinum (1,000,000 copper = 1 platinum)
if remaining >= CoinsPlatinumThreshold {
coins.Platinum = int32(remaining / CoinsPlatinumThreshold)
remaining -= int64(coins.Platinum) * CoinsPlatinumThreshold
}
// Calculate gold (10,000 copper = 1 gold)
if remaining >= CoinsGoldThreshold {
coins.Gold = int32(remaining / CoinsGoldThreshold)
remaining -= int64(coins.Gold) * CoinsGoldThreshold
}
// Calculate silver (100 copper = 1 silver)
if remaining >= CoinsSilverThreshold {
coins.Silver = int32(remaining / CoinsSilverThreshold)
remaining -= int64(coins.Silver) * CoinsSilverThreshold
}
// Remaining is copper
if remaining > 0 {
coins.Copper = int32(remaining)
}
return coins
}
// CoinsToCopper converts coin denominations to total copper
func CoinsToCopper(coins CoinAmounts) int64 {
total := int64(coins.Copper)
total += int64(coins.Silver) * CoinsSilverThreshold
total += int64(coins.Gold) * CoinsGoldThreshold
total += int64(coins.Platinum) * CoinsPlatinumThreshold
return total
}
// FormatCoins returns a human-readable string representation of coins
func FormatCoins(totalCopper int64) string {
if totalCopper == 0 {
return "0 copper"
}
coins := CalculateCoins(totalCopper)
parts := make([]string, 0, 4)
if coins.Platinum > 0 {
parts = append(parts, fmt.Sprintf("%d platinum", coins.Platinum))
}
if coins.Gold > 0 {
parts = append(parts, fmt.Sprintf("%d gold", coins.Gold))
}
if coins.Silver > 0 {
parts = append(parts, fmt.Sprintf("%d silver", coins.Silver))
}
if coins.Copper > 0 {
parts = append(parts, fmt.Sprintf("%d copper", coins.Copper))
}
return strings.Join(parts, ", ")
}
// ValidateTradeSlot checks if a slot number is valid for a participant
func ValidateTradeSlot(slot int8, maxSlots int8) bool {
return slot >= 0 && slot < maxSlots
}
// ValidateTradeQuantity checks if a quantity is valid for trading
func ValidateTradeQuantity(quantity, available int32) bool {
return quantity > 0 && quantity <= available
}
// FormatTradeError returns a formatted error message for trade operations
func FormatTradeError(code int32) string {
switch code {
case TradeResultSuccess:
return "Success"
case TradeResultAlreadyInTrade:
return "Item is already in the trade"
case TradeResultNoTrade:
return "Item cannot be traded"
case TradeResultHeirloom:
return "Heirloom item cannot be traded to this player"
case TradeResultInvalidSlot:
return "Invalid or occupied trade slot"
case TradeResultSlotOutOfRange:
return "Trade slot is out of range"
case TradeResultInsufficientQty:
return "Insufficient quantity to trade"
default:
return fmt.Sprintf("Unknown trade error: %d", code)
}
}
// GetClientMaxSlots returns the maximum trade slots for a client version
// Converted from C++ Trade constructor logic
func GetClientMaxSlots(clientVersion int32) int8 {
if clientVersion <= 561 {
return TradeMaxSlotsLegacy
}
return TradeMaxSlotsDefault
}
// IsValidTradeState checks if a trade operation is valid for the current state
func IsValidTradeState(state TradeState, operation string) bool {
switch operation {
case "add_item", "remove_item", "add_coins", "remove_coins", "accept":
return state == TradeStateActive
case "cancel":
return state == TradeStateActive
case "complete":
return state == TradeStateAccepted
default:
return false
}
}
// GenerateTradeLogEntry creates a log entry for trade operations
func GenerateTradeLogEntry(tradeID string, operation string, entityID int32, details interface{}) string {
return fmt.Sprintf("[Trade:%s] %s by entity %d: %v", tradeID, operation, entityID, details)
}
// CompareTradeItems checks if two trade item infos are equivalent
func CompareTradeItems(item1, item2 TradeItemInfo) bool {
if item1.Item == nil && item2.Item == nil {
return item1.Quantity == item2.Quantity
}
if item1.Item == nil || item2.Item == nil {
return false
}
return item1.Item.GetID() == item2.Item.GetID() &&
item1.Quantity == item2.Quantity
}
// CalculateTradeValue estimates the total value of items and coins in a trade
// This is a helper function for trade balancing and analysis
func CalculateTradeValue(participant *TradeParticipant) map[string]interface{} {
value := make(map[string]interface{})
// Add coin value
value["coins"] = participant.Coins
value["coins_formatted"] = FormatCoins(participant.Coins)
// Add item information
itemCount := len(participant.Items)
value["item_count"] = itemCount
if itemCount > 0 {
items := make([]map[string]interface{}, 0, itemCount)
for slot, itemInfo := range participant.Items {
itemData := make(map[string]interface{})
itemData["slot"] = slot
itemData["quantity"] = itemInfo.Quantity
if itemInfo.Item != nil {
itemData["item_id"] = itemInfo.Item.GetID()
itemData["item_name"] = itemInfo.Item.GetName()
}
items = append(items, itemData)
}
value["items"] = items
}
return value
}
// ValidateTradeCompletion checks if a trade is ready to be completed
func ValidateTradeCompletion(trade *Trade) []string {
errors := make([]string, 0)
if trade.GetState() != TradeStateActive {
errors = append(errors, "Trade is not in active state")
return errors
}
// Check if both parties have accepted
trader1Accepted := trade.HasAcceptedTrade(trade.GetTrader1ID())
trader2Accepted := trade.HasAcceptedTrade(trade.GetTrader2ID())
if !trader1Accepted {
errors = append(errors, "Trader 1 has not accepted the trade")
}
if !trader2Accepted {
errors = append(errors, "Trader 2 has not accepted the trade")
}
// TODO: Add additional validation when entity system is available:
// - Verify entities still exist and are online
// - Check inventory space for received items
// - Validate coin amounts against actual entity wealth
// - Check for item/trade restrictions that may have changed
return errors
}