397 lines
14 KiB
Go

package tradeskills
import (
"fmt"
"log"
"math"
)
// PacketBuilder handles the construction of tradeskill-related packets.
type PacketBuilder interface {
// SendCreateFromRecipe builds and sends the recipe crafting UI packet
SendCreateFromRecipe(clientID uint32, recipeID uint32) error
// SendItemCreationUI builds and sends the item creation/crafting progress UI packet
SendItemCreationUI(clientID uint32, recipeID uint32) error
// StopCrafting sends the stop crafting packet
StopCrafting(clientID uint32) error
// CounterReaction sends the event counter reaction packet
CounterReaction(clientID uint32, countered bool) error
// UpdateCreateItem sends crafting progress update packet
UpdateCreateItem(clientID uint32, update CraftingUpdate) error
}
// CraftingUpdate represents data for a crafting progress update packet.
type CraftingUpdate struct {
TableSpawnID uint32 // ID of the crafting table spawn
Effect int8 // Effect type (1=crit success, 2=success, 3=failure, 4=crit failure)
TotalDurability int32 // Current total durability
TotalProgress int32 // Current total progress
DurabilityChange int32 // Change in durability this update
ProgressChange int32 // Change in progress this update
ProgressLevel int8 // Progress level (0-4)
Event *TradeskillEvent // Active event (if any)
}
// RecipeComponentInfo represents component information for recipe UI.
type RecipeComponentInfo struct {
ItemID uint32 // Item ID
UniqueID uint32 // Unique item instance ID
Name string // Item name
Icon uint32 // Item icon
Quantity int16 // Available quantity
QuantityUsed int16 // Quantity being used
}
// RecipeUIData represents all data needed for the recipe creation UI.
type RecipeUIData struct {
RecipeID uint32 // Recipe ID
RecipeName string // Recipe name
CraftingStation string // Required crafting station
Tier int8 // Recipe tier
ProductName string // Product name
ProductIcon uint32 // Product icon
ProductQuantity int16 // Product quantity
PrimaryTitle string // Primary component title
PrimaryQuantityNeeded int16 // Primary component quantity needed
PrimaryComponents []RecipeComponentInfo // Available primary components
PrimarySelected []RecipeComponentInfo // Selected primary components
BuildComponents [][]RecipeComponentInfo // Build components (slots 1-4)
BuildSelected [][]RecipeComponentInfo // Selected build components
BuildTitles []string // Build component titles
BuildQuantitiesNeeded []int16 // Build component quantities needed
FuelTitle string // Fuel component title
FuelQuantityNeeded int16 // Fuel component quantity needed
FuelComponents []RecipeComponentInfo // Available fuel components
FuelSelected []RecipeComponentInfo // Selected fuel components
MassProductionChoices []int32 // Mass production quantity choices
}
// ItemCreationUIData represents data for the item creation progress UI.
type ItemCreationUIData struct {
MaxPossibleDurability int32 // Maximum durability (1000)
MaxPossibleProgress int32 // Maximum progress (1000)
ProductProgressNeeded int32 // Progress needed for completion (1000)
ProgressLevelsKnown int8 // Highest stage known by player
ProcessStages []ItemCreationStage // Process stages (0-3)
ProductItem ItemCreationProduct // Final product
SkillIDs []uint32 // Available skill IDs for crafting
}
// ItemCreationStage represents a stage in the item creation process.
type ItemCreationStage struct {
ProgressNeeded int32 // Progress needed for this stage
Product ItemCreationProduct // Product for this stage
Byproduct ItemCreationProduct // Byproduct for this stage (optional)
}
// ItemCreationProduct represents a product in item creation.
type ItemCreationProduct struct {
Name string // Product name
Icon uint32 // Product icon
}
// DefaultPacketBuilder is a basic implementation of PacketBuilder.
// In a real implementation, this would interface with the actual packet system.
type DefaultPacketBuilder struct {
// TODO: Add dependencies for actual packet building (client manager, item manager, etc.)
}
// NewDefaultPacketBuilder creates a new default packet builder.
func NewDefaultPacketBuilder() *DefaultPacketBuilder {
return &DefaultPacketBuilder{}
}
// SendCreateFromRecipe builds and sends the recipe crafting UI packet.
func (pb *DefaultPacketBuilder) SendCreateFromRecipe(clientID uint32, recipeID uint32) error {
// TODO: Implement actual packet building
// This is a placeholder implementation showing the data structure
log.Printf("Building WS_CreateFromRecipe packet for client %d, recipe %d", clientID, recipeID)
// In the real implementation, this would:
// 1. Get recipe from master recipe list
// 2. Validate player has recipe
// 3. Validate crafting table
// 4. Build component lists
// 5. Calculate mass production options
// 6. Create and send packet
uiData := RecipeUIData{
RecipeID: recipeID,
RecipeName: "Example Recipe",
CraftingStation: "Forge",
Tier: 1,
ProductName: "Iron Sword",
ProductIcon: 12345,
ProductQuantity: 1,
PrimaryTitle: "Metal",
PrimaryQuantityNeeded: 2,
MassProductionChoices: []int32{1, 5, 10, 15, 20, 25},
}
// Add example primary components
uiData.PrimaryComponents = []RecipeComponentInfo{
{ItemID: 1001, UniqueID: 5001, Name: "Iron Ingot", Icon: 100, Quantity: 5, QuantityUsed: 2},
{ItemID: 1002, UniqueID: 5002, Name: "Steel Ingot", Icon: 101, Quantity: 3, QuantityUsed: 2},
}
// TODO: Send actual packet to client
log.Printf("Would send recipe UI data: %+v", uiData)
return nil
}
// SendItemCreationUI builds and sends the item creation/crafting progress UI packet.
func (pb *DefaultPacketBuilder) SendItemCreationUI(clientID uint32, recipeID uint32) error {
log.Printf("Building WS_ShowItemCreation packet for client %d, recipe %d", clientID, recipeID)
// TODO: Implement actual packet building
// This would build the crafting progress window with stages
uiData := ItemCreationUIData{
MaxPossibleDurability: MaxDurability,
MaxPossibleProgress: MaxProgress,
ProductProgressNeeded: MaxProgress,
ProgressLevelsKnown: 0, // Player's highest known stage
}
// Add process stages (0-3, stage 4 is completion)
for i := 0; i < 4; i++ {
stage := ItemCreationStage{
Product: ItemCreationProduct{
Name: fmt.Sprintf("Stage %d Product", i),
Icon: uint32(1000 + i),
},
}
switch i {
case 0:
stage.ProgressNeeded = 0 // Stage 0 is fuel/byproduct
case 1:
stage.ProgressNeeded = ProgressStage1
case 2:
stage.ProgressNeeded = ProgressStage2
case 3:
stage.ProgressNeeded = ProgressStage3
}
uiData.ProcessStages = append(uiData.ProcessStages, stage)
}
// Final product (stage 4)
uiData.ProductItem = ItemCreationProduct{
Name: "Completed Item",
Icon: 2000,
}
// TODO: Add skills for tradeskill techniques
uiData.SkillIDs = []uint32{
// These would be populated from player's spellbook based on recipe technique
}
log.Printf("Would send item creation UI data: %+v", uiData)
return nil
}
// StopCrafting sends the stop crafting packet.
func (pb *DefaultPacketBuilder) StopCrafting(clientID uint32) error {
log.Printf("Sending OP_StopItemCreationMsg to client %d", clientID)
// TODO: Send actual packet
// This would be a simple packet with opcode OP_StopItemCreationMsg and no data
return nil
}
// CounterReaction sends the event counter reaction packet.
func (pb *DefaultPacketBuilder) CounterReaction(clientID uint32, countered bool) error {
log.Printf("Sending WS_TSEventReaction to client %d, countered=%v", clientID, countered)
// TODO: Build and send WS_TSEventReaction packet
// packet->setDataByName("counter_reaction", countered ? 1 : 0);
return nil
}
// UpdateCreateItem sends crafting progress update packet.
func (pb *DefaultPacketBuilder) UpdateCreateItem(clientID uint32, update CraftingUpdate) error {
log.Printf("Sending WS_UpdateCreateItem to client %d: progress=%d, durability=%d, effect=%d",
clientID, update.TotalProgress, update.TotalDurability, update.Effect)
// Calculate progress level based on current progress
progressLevel := int8(0)
if update.TotalProgress >= MaxProgress {
progressLevel = 4
} else if update.TotalProgress >= ProgressStage3 {
progressLevel = 3
} else if update.TotalProgress >= ProgressStage2 {
progressLevel = 2
} else if update.TotalProgress >= ProgressStage1 {
progressLevel = 1
}
update.ProgressLevel = progressLevel
// TODO: Build and send actual WS_UpdateCreateItem packet
// This would include:
// - spawn_id (table spawn ID)
// - effect (1=crit success, 2=success, 3=failure, 4=crit failure)
// - total_durability
// - total_progress
// - durability_change
// - progress_change
// - progress_level
// - reaction_icon (if event)
// - reaction_name (if event)
if update.Event != nil {
log.Printf("Including event in update: %s (icon: %d)", update.Event.Name, update.Event.Icon)
}
return nil
}
// PacketHelper provides utility functions for packet building.
type PacketHelper struct{}
// CalculateProgressStage determines the progress stage based on current progress.
func (ph *PacketHelper) CalculateProgressStage(progress int32) int8 {
if progress >= MaxProgress {
return 4
} else if progress >= ProgressStage3 {
return 3
} else if progress >= ProgressStage2 {
return 2
} else if progress >= ProgressStage1 {
return 1
}
return 0
}
// GetMassProductionQuantities returns the available mass production quantities.
func (ph *PacketHelper) GetMassProductionQuantities(maxLevel int) []int32 {
// Base quantities
quantities := []int32{1}
// Add additional quantities based on achievement/level
// This matches the C++ logic: v{1,2,4,6,11,21}[mp] where mp is 5
maxQuantities := []int32{1, 2, 4, 6, 11, 21}
for i := 1; i < len(maxQuantities) && i <= maxLevel; i++ {
quantities = append(quantities, maxQuantities[i]*5)
}
return quantities
}
// ValidateRecipeComponents checks if the player has the required components.
func (ph *PacketHelper) ValidateRecipeComponents(playerID uint32, components []ComponentUsage) error {
// TODO: Implement component validation
// This would check player inventory for required items and quantities
if len(components) == 0 {
return fmt.Errorf("no components provided")
}
for _, component := range components {
if component.ItemUniqueID == 0 {
return fmt.Errorf("invalid component unique ID")
}
if component.Quantity <= 0 {
return fmt.Errorf("invalid component quantity: %d", component.Quantity)
}
}
log.Printf("Component validation passed for player %d", playerID)
return nil
}
// CalculateItemPacketType determines the packet type based on client version.
func (ph *PacketHelper) CalculateItemPacketType(clientVersion int16) int16 {
// TODO: Implement version-specific packet type calculation
// This would match the GetItemPacketType function from the C++ code
if clientVersion < 860 {
return 1 // Older packet format
} else if clientVersion < 1193 {
return 2 // Middle packet format
} else {
return 3 // Newer packet format
}
}
// GetClientItemPacketOffset returns the item packet offset for a client version.
func (ph *PacketHelper) GetClientItemPacketOffset(clientVersion int16) int32 {
// TODO: Implement version-specific offset calculation
// This matches client->GetClientItemPacketOffset() from C++
if clientVersion < 860 {
return -1 // Use default offset
}
return 2 // Standard offset for newer clients
}
// ComponentSlotInfo represents information about recipe component slots.
type ComponentSlotInfo struct {
SlotID int8 // Component slot (0=primary, 1-4=build, 5=fuel)
Title string // Component title/name
QuantityReq int16 // Required quantity
QuantityHave int16 // Available quantity
Items []RecipeComponentInfo // Available items for this slot
}
// BuildComponentSlots creates component slot information for a recipe.
func (ph *PacketHelper) BuildComponentSlots(recipeID uint32, playerID uint32) ([]ComponentSlotInfo, error) {
// TODO: Implement actual component slot building
// This would query the recipe and player inventory to build component options
slots := []ComponentSlotInfo{
{
SlotID: ComponentSlotPrimary,
Title: "Primary Component",
QuantityReq: 2,
QuantityHave: 5,
Items: []RecipeComponentInfo{
{ItemID: 1001, UniqueID: 5001, Name: "Iron Ingot", Icon: 100, Quantity: 5},
},
},
{
SlotID: ComponentSlotFuel,
Title: "Fuel",
QuantityReq: 1,
QuantityHave: 3,
Items: []RecipeComponentInfo{
{ItemID: 2001, UniqueID: 6001, Name: "Coal", Icon: 200, Quantity: 3},
},
},
}
return slots, nil
}
// EffectTypeFromOutcome converts a crafting outcome to an effect type.
func (ph *PacketHelper) EffectTypeFromOutcome(outcome CraftingOutcome) int8 {
if outcome.CriticalSuccess {
return 1 // Critical success
} else if outcome.Success {
return 2 // Regular success
} else if outcome.CriticalFailure {
return 4 // Critical failure
} else {
return 3 // Regular failure
}
}
// ClampProgress ensures progress values are within valid ranges.
func (ph *PacketHelper) ClampProgress(progress int32) int32 {
return int32(math.Max(float64(MinProgress), math.Min(float64(MaxProgress), float64(progress))))
}
// ClampDurability ensures durability values are within valid ranges.
func (ph *PacketHelper) ClampDurability(durability int32) int32 {
return int32(math.Max(float64(MinDurability), math.Min(float64(MaxDurability), float64(durability))))
}