package tradeskills import ( "fmt" "time" ) // PlayerManager defines the interface for player-related operations needed by tradeskills. type PlayerManager interface { // GetPlayer retrieves player information by ID GetPlayer(playerID uint32) (Player, error) // GetPlayerTarget gets the current target of a player GetPlayerTarget(playerID uint32) (Spawn, error) // SetPlayerVisualState sets the visual animation state for a player SetPlayerVisualState(playerID uint32, animationID uint32) error // SendMessageToPlayer sends a message to a player SendMessageToPlayer(playerID uint32, channel int8, message string) error } // ItemManager defines the interface for item-related operations needed by tradeskills. type ItemManager interface { // GetItem retrieves item information by ID GetItem(itemID uint32) (Item, error) // GetPlayerItem gets a specific item from a player's inventory GetPlayerItem(playerID uint32, uniqueID uint32) (Item, error) // GetPlayerItemsByID gets all items of a specific type from player inventory GetPlayerItemsByID(playerID uint32, itemID uint32) ([]Item, error) // ConsumePlayerItems removes items from player inventory ConsumePlayerItems(playerID uint32, components []ComponentUsage) error // GiveItemToPlayer adds an item to player inventory GiveItemToPlayer(playerID uint32, itemID uint32, quantity int16, creator string) error // LockPlayerItems locks items in player inventory for crafting LockPlayerItems(playerID uint32, components []ComponentUsage) error // UnlockPlayerItems unlocks previously locked items UnlockPlayerItems(playerID uint32, components []ComponentUsage) error } // RecipeManager defines the interface for recipe-related operations. type RecipeManager interface { // GetRecipe retrieves recipe information by ID GetRecipe(recipeID uint32) (Recipe, error) // GetPlayerRecipe gets a player's copy of a recipe (with progress tracking) GetPlayerRecipe(playerID uint32, recipeID uint32) (PlayerRecipe, error) // UpdatePlayerRecipe updates a player's recipe progress UpdatePlayerRecipe(playerID uint32, recipeID uint32, highestStage int8) error // ValidateRecipeComponents checks if player has required components ValidateRecipeComponents(playerID uint32, recipeID uint32, components []ComponentUsage) error } // SpellManager defines the interface for spell-related operations needed by tradeskills. type SpellManager interface { // GetPlayerTradeskillSpells gets tradeskill spells for a player GetPlayerTradeskillSpells(playerID uint32, technique uint32) ([]Spell, error) // LockTradeskillSpells locks tradeskill spells for a player LockTradeskillSpells(playerID uint32) error // UnlockTradeskillSpells unlocks tradeskill spells for a player UnlockTradeskillSpells(playerID uint32) error } // ZoneManager defines the interface for zone-related operations. type ZoneManager interface { // GetSpawn retrieves spawn information by ID GetSpawn(spawnID uint32) (Spawn, error) // PlayAnimation plays an animation for a spawn PlayAnimation(spawnID uint32, animationID uint32) error // ValidateCraftingTable checks if a spawn is a valid crafting table for a recipe ValidateCraftingTable(spawnID uint32, requiredDevice string) error } // ExperienceManager defines the interface for experience-related operations. type ExperienceManager interface { // CalculateTradeskillXP calculates XP for a recipe level CalculateTradeskillXP(playerID uint32, recipeLevel int16) (float32, error) // AwardTradeskillXP gives tradeskill XP to a player AwardTradeskillXP(playerID uint32, xp int32) (bool, error) // Returns true if level changed // GetPlayerTradeskillLevel gets a player's current tradeskill level GetPlayerTradeskillLevel(playerID uint32) (int16, error) } // QuestManager defines the interface for quest-related operations. type QuestManager interface { // CheckCraftingQuests checks for quest updates related to crafting CheckCraftingQuests(playerID uint32, itemID uint32, quantity int8) error } // RuleManager defines the interface for rules/configuration access. type RuleManager interface { // GetTradeskillSuccessChance gets the base success chance percentage GetTradeskillSuccessChance() float32 // GetTradeskillCritSuccessChance gets the critical success chance percentage GetTradeskillCritSuccessChance() float32 // GetTradeskillFailChance gets the base failure chance percentage GetTradeskillFailChance() float32 // GetTradeskillCritFailChance gets the critical failure chance percentage GetTradeskillCritFailChance() float32 // GetTradeskillEventChance gets the event trigger chance percentage GetTradeskillEventChance() float32 } // Data structures used by the interfaces // Player represents a player in the game. type Player struct { ID uint32 Name string CurrentRecipe uint32 TradeskillLevel int16 SuccessModifier int16 // Stat bonus to success chance ProgressModifier int16 // Stat bonus to progress DurabilityModifier int16 // Stat bonus to durability } // Item represents an item in the game. type Item struct { ID uint32 UniqueID uint32 Name string Icon uint32 Count int16 Creator string StackCount int16 } // Recipe represents a crafting recipe. type Recipe struct { ID uint32 Name string Level int16 Tier int8 Technique uint32 Device string ProductID uint32 ProductQuantity int16 PrimaryComponentTitle string PrimaryComponentQuantity int16 Build1ComponentTitle string Build1ComponentQuantity int16 Build2ComponentTitle string Build2ComponentQuantity int16 Build3ComponentTitle string Build3ComponentQuantity int16 Build4ComponentTitle string Build4ComponentQuantity int16 FuelComponentTitle string FuelComponentQuantity int16 Components map[int8][]uint32 // Component slot -> item IDs Products map[int8]*RecipeProduct // Stage -> product } // PlayerRecipe represents a player's version of a recipe with progress tracking. type PlayerRecipe struct { RecipeID uint32 PlayerID uint32 HighestStage int8 // Bitmask of completed stages } // RecipeProduct represents a product from a recipe stage. type RecipeProduct struct { ProductID uint32 ProductQty int16 ByproductID uint32 ByproductQty int16 } // Spell represents a spell/ability. type Spell struct { ID uint32 Name string Icon int16 TechniqueSlot int8 // Location index for tradeskill UI } // Spawn represents a spawn (NPC, object, etc.) in the game world. type Spawn struct { ID uint32 Name string IsObject bool DeviceID uint32 // For crafting tables } // TradeskillSystemAdapter provides a high-level interface to the complete tradeskill system. type TradeskillSystemAdapter struct { manager *TradeskillManager eventsList *MasterTradeskillEventsList database DatabaseService packetBuilder PacketBuilder playerManager PlayerManager itemManager ItemManager recipeManager RecipeManager spellManager SpellManager zoneManager ZoneManager experienceManager ExperienceManager questManager QuestManager ruleManager RuleManager } // NewTradeskillSystemAdapter creates a new system adapter with all dependencies. func NewTradeskillSystemAdapter( manager *TradeskillManager, eventsList *MasterTradeskillEventsList, database DatabaseService, packetBuilder PacketBuilder, playerManager PlayerManager, itemManager ItemManager, recipeManager RecipeManager, spellManager SpellManager, zoneManager ZoneManager, experienceManager ExperienceManager, questManager QuestManager, ruleManager RuleManager, ) *TradeskillSystemAdapter { return &TradeskillSystemAdapter{ manager: manager, eventsList: eventsList, database: database, packetBuilder: packetBuilder, playerManager: playerManager, itemManager: itemManager, recipeManager: recipeManager, spellManager: spellManager, zoneManager: zoneManager, experienceManager: experienceManager, questManager: questManager, ruleManager: ruleManager, } } // Initialize sets up the tradeskill system (loads events, updates config, etc.). func (tsa *TradeskillSystemAdapter) Initialize() error { // Load tradeskill events from database err := tsa.database.LoadTradeskillEvents(tsa.eventsList) if err != nil { return err } // Update manager configuration from rules err = tsa.updateManagerConfig() if err != nil { return err } return nil } // StartCrafting begins a crafting session with full validation and setup. func (tsa *TradeskillSystemAdapter) StartCrafting(playerID uint32, recipeID uint32, components []ComponentUsage) error { // Get player info _, err := tsa.playerManager.GetPlayer(playerID) if err != nil { return err } // Get target (crafting table) target, err := tsa.playerManager.GetPlayerTarget(playerID) if err != nil { return err } // Get recipe recipe, err := tsa.recipeManager.GetRecipe(recipeID) if err != nil { return err } // Validate crafting table err = tsa.zoneManager.ValidateCraftingTable(target.ID, recipe.Device) if err != nil { return err } // Validate components err = tsa.recipeManager.ValidateRecipeComponents(playerID, recipeID, components) if err != nil { return err } // Lock inventory items err = tsa.itemManager.LockPlayerItems(playerID, components) if err != nil { return err } // Send recipe UI packet err = tsa.packetBuilder.SendCreateFromRecipe(playerID, recipeID) if err != nil { tsa.itemManager.UnlockPlayerItems(playerID, components) // Cleanup on error return err } // Send item creation UI packet err = tsa.packetBuilder.SendItemCreationUI(playerID, recipeID) if err != nil { tsa.itemManager.UnlockPlayerItems(playerID, components) // Cleanup on error return err } // Start crafting session request := CraftingRequest{ PlayerID: playerID, RecipeID: recipeID, TableSpawnID: target.ID, Components: components, Quantity: 1, // TODO: Support mass production } err = tsa.manager.BeginCrafting(request) if err != nil { tsa.itemManager.UnlockPlayerItems(playerID, components) // Cleanup on error return err } // Unlock tradeskill spells err = tsa.spellManager.UnlockTradeskillSpells(playerID) if err != nil { // Not critical, just log warning tsa.playerManager.SendMessageToPlayer(playerID, 1, "Warning: Failed to unlock tradeskill spells") } return nil } // StopCrafting ends a crafting session with full cleanup and rewards. func (tsa *TradeskillSystemAdapter) StopCrafting(playerID uint32) error { // Get tradeskill session tradeskill := tsa.manager.GetTradeskill(playerID) if tradeskill == nil { return nil // Not crafting } // Calculate completion stage and rewards err := tsa.processCompletionRewards(playerID, tradeskill) if err != nil { // Log error but continue with cleanup tsa.playerManager.SendMessageToPlayer(playerID, 1, "Warning: Failed to process completion rewards") } // Stop the crafting session err = tsa.manager.StopCrafting(playerID) if err != nil { return err } // Send stop crafting packet err = tsa.packetBuilder.StopCrafting(playerID) if err != nil { return err } // Unlock inventory items err = tsa.itemManager.UnlockPlayerItems(playerID, tradeskill.UsedComponents) if err != nil { // Log warning but continue tsa.playerManager.SendMessageToPlayer(playerID, 1, "Warning: Failed to unlock inventory items") } // Lock tradeskill spells err = tsa.spellManager.LockTradeskillSpells(playerID) if err != nil { // Not critical, just log warning tsa.playerManager.SendMessageToPlayer(playerID, 1, "Warning: Failed to lock tradeskill spells") } // Reset player visual state err = tsa.playerManager.SetPlayerVisualState(playerID, 0) if err != nil { // Not critical, just log warning tsa.playerManager.SendMessageToPlayer(playerID, 1, "Warning: Failed to reset visual state") } return nil } // ProcessCraftingUpdates handles periodic processing with full integration. func (tsa *TradeskillSystemAdapter) ProcessCraftingUpdates() { // Run the core manager processing tsa.manager.Process() // TODO: Handle any additional processing needed // This could include sending update packets, triggering events, etc. } // HandleEventCounter processes a player's attempt to counter a tradeskill event. func (tsa *TradeskillSystemAdapter) HandleEventCounter(playerID uint32, spellIcon int16) error { request := EventCounterRequest{ PlayerID: playerID, SpellIcon: spellIcon, } // Process the counter attempt err := tsa.manager.CheckTradeskillEvent(request) if err != nil { return err } // Get the result and send reaction packet tradeskill := tsa.manager.GetTradeskill(playerID) if tradeskill != nil && tradeskill.EventChecked { err = tsa.packetBuilder.CounterReaction(playerID, tradeskill.EventCountered) if err != nil { return err } // Send message to player action := "failed to counter" if tradeskill.EventCountered { action = "successfully countered" } if tradeskill.CurrentEvent != nil { message := fmt.Sprintf("You %s %s.", action, tradeskill.CurrentEvent.Name) tsa.playerManager.SendMessageToPlayer(playerID, 2, message) // CHANNEL_NARRATIVE } } return nil } // updateManagerConfig updates the manager with current rule values. func (tsa *TradeskillSystemAdapter) updateManagerConfig() error { critFail := tsa.ruleManager.GetTradeskillCritFailChance() critSuccess := tsa.ruleManager.GetTradeskillCritSuccessChance() fail := tsa.ruleManager.GetTradeskillFailChance() success := tsa.ruleManager.GetTradeskillSuccessChance() eventChance := tsa.ruleManager.GetTradeskillEventChance() return tsa.manager.UpdateConfiguration(critFail, critSuccess, fail, success, eventChance) } // processCompletionRewards handles giving rewards when crafting completes. func (tsa *TradeskillSystemAdapter) processCompletionRewards(playerID uint32, ts *Tradeskill) error { // Get recipe recipe, err := tsa.recipeManager.GetRecipe(ts.RecipeID) if err != nil { return err } // Determine completion stage based on progress and durability stage := tsa.calculateCompletionStage(ts.CurrentProgress, ts.CurrentDurability) // Give appropriate rewards for the stage if stage >= 0 && recipe.Products != nil { if product, exists := recipe.Products[stage]; exists { // Give main product if product.ProductID > 0 { err = tsa.itemManager.GiveItemToPlayer(playerID, product.ProductID, product.ProductQty, "") if err != nil { return err } // Update quests tsa.questManager.CheckCraftingQuests(playerID, product.ProductID, int8(product.ProductQty)) } // Give byproduct if any if product.ByproductID > 0 { err = tsa.itemManager.GiveItemToPlayer(playerID, product.ByproductID, product.ByproductQty, "") if err != nil { return err } // Update quests tsa.questManager.CheckCraftingQuests(playerID, product.ByproductID, int8(product.ByproductQty)) } } } // Award tradeskill experience baseXP, err := tsa.experienceManager.CalculateTradeskillXP(playerID, recipe.Level) if err == nil && baseXP > 0 { // Apply stage-based XP reduction xpMultiplier := tsa.getXPMultiplierForStage(stage) finalXP := int32(baseXP * xpMultiplier) if finalXP > 0 { levelChanged, err := tsa.experienceManager.AwardTradeskillXP(playerID, finalXP) if err == nil { // Notify player of XP gain message := fmt.Sprintf("You gain %d Tradeskill XP!", finalXP) tsa.playerManager.SendMessageToPlayer(playerID, 3, message) // CHANNEL_REWARD // Handle level change if needed if levelChanged { // TODO: Handle tradeskill level up } } } } // Update player recipe progress playerRecipe, err := tsa.recipeManager.GetPlayerRecipe(playerID, ts.RecipeID) if err == nil { newStage := tsa.updatePlayerRecipeStage(playerRecipe.HighestStage, stage) if newStage != playerRecipe.HighestStage { tsa.recipeManager.UpdatePlayerRecipe(playerID, ts.RecipeID, newStage) } } // Play success/failure animation technique := recipe.Technique clientVersion := int16(1200) // TODO: Get actual client version var animationID uint32 if stage == 4 { // Full completion animationID = tsa.manager.GetTechniqueSuccessAnim(clientVersion, technique) } else { animationID = tsa.manager.GetTechniqueFailureAnim(clientVersion, technique) } if animationID > 0 { tsa.zoneManager.PlayAnimation(playerID, animationID) } return nil } // calculateCompletionStage determines the completion stage based on progress/durability. func (tsa *TradeskillSystemAdapter) calculateCompletionStage(progress, durability int32) int8 { if durability >= 800 && progress >= 1000 { return 4 // Perfect completion } else if (durability >= 200 && durability < 800 && progress >= 800) || (durability >= 800 && progress >= 800 && progress < 1000) { return 3 // Stage 3 } else if (durability < 200 && progress >= 600) || (durability >= 200 && progress >= 600 && progress < 800) { return 2 // Stage 2 } else if progress >= 400 && progress < 600 { return 1 // Stage 1 } else if progress < 400 { return 0 // Stage 0 (fuel/byproduct) } return 0 } // getXPMultiplierForStage returns the XP multiplier for a completion stage. func (tsa *TradeskillSystemAdapter) getXPMultiplierForStage(stage int8) float32 { switch stage { case 4: return 1.0 // Full XP for perfect completion case 3: return 0.85 // 85% XP for stage 3 case 2: return 0.70 // 70% XP for stage 2 case 1: return 0.55 // 55% XP for stage 1 case 0: return 0.0 // No XP for stage 0 default: return 0.0 } } // updatePlayerRecipeStage updates the player's recipe stage progress bitmask. func (tsa *TradeskillSystemAdapter) updatePlayerRecipeStage(currentStage int8, completedStage int8) int8 { // Set the bit for the completed stage switch completedStage { case 1: if (currentStage & 1) == 0 { return currentStage + 1 } case 2: if (currentStage & 2) == 0 { return currentStage + 2 } case 3: if (currentStage & 4) == 0 { return currentStage + 4 } case 4: if (currentStage & 8) == 0 { return currentStage + 8 } } return currentStage } // GetSystemStats returns comprehensive statistics about the tradeskill system. func (tsa *TradeskillSystemAdapter) GetSystemStats() map[string]interface{} { managerStats := tsa.manager.GetStats() eventsStats := tsa.eventsList.GetStats() return map[string]interface{}{ "active_sessions": managerStats.ActiveSessions, "recent_completions": managerStats.RecentCompletions, "average_session_time": managerStats.AverageSessionTime, "total_events": eventsStats.TotalEvents, "events_by_technique": eventsStats.EventsByTechnique, "last_update": time.Now(), } }