397 lines
14 KiB
Go
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))))
|
|
}
|