eq2go/internal/tradeskills/interfaces.go

612 lines
19 KiB
Go

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]any {
managerStats := tsa.manager.GetStats()
eventsStats := tsa.eventsList.GetStats()
return map[string]any{
"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(),
}
}