package traits import ( "fmt" "time" ) // SpellManager defines the interface for spell-related operations needed by traits. type SpellManager interface { // GetSpell retrieves spell information by ID and tier GetSpell(spellID uint32, tier int8) (Spell, error) // GetPlayerSpells gets all spells known by a player GetPlayerSpells(playerID uint32) ([]uint32, error) // PlayerHasSpell checks if a player has a specific spell PlayerHasSpell(playerID uint32, spellID uint32, tier int8) (bool, error) // GetSpellsBySkill gets spells associated with a skill GetSpellsBySkill(playerID uint32, skillID uint32) ([]uint32, error) } // ItemManager defines the interface for item-related operations needed by traits. type ItemManager interface { // GetItem retrieves item information by ID GetItem(itemID uint32) (Item, error) // GetPlayerItems gets items from a player's inventory GetPlayerItems(playerID uint32) ([]Item, error) // GiveItemToPlayer adds an item to a player's inventory GiveItemToPlayer(playerID uint32, itemID uint32, quantity int16) error } // PlayerManager defines the interface for player-related operations needed by traits. type PlayerManager interface { // GetPlayer retrieves player information by ID GetPlayer(playerID uint32) (Player, error) // GetPlayerLevel gets a player's current level GetPlayerLevel(playerID uint32) (int16, error) // GetPlayerClass gets a player's adventure class GetPlayerClass(playerID uint32) (int8, error) // GetPlayerRace gets a player's race GetPlayerRace(playerID uint32) (int8, error) // SendMessageToPlayer sends a message to a player SendMessageToPlayer(playerID uint32, channel int8, message string) error } // PacketManager defines the interface for packet-related operations. type PacketManager interface { // SendPacketToPlayer sends a packet to a specific player SendPacketToPlayer(playerID uint32, packetData []byte) error // QueuePacketForPlayer queues a packet for delayed sending QueuePacketForPlayer(playerID uint32, packetData []byte) error // GetClientVersion gets the client version for a player GetClientVersion(playerID uint32) (int16, error) } // RuleManager defines the interface for rules/configuration access. type RuleManager interface { // GetBool retrieves a boolean rule value GetBool(category, rule string) bool // GetInt32 retrieves an int32 rule value GetInt32(category, rule string) int32 // GetFloat retrieves a float rule value GetFloat(category, rule string) float32 } // DatabaseService defines the interface for trait persistence operations. type DatabaseService interface { // LoadTraits loads all traits from the database LoadTraits(masterList *MasterTraitList) error // SaveTrait saves a trait to the database SaveTrait(trait *TraitData) error // DeleteTrait removes a trait from the database DeleteTrait(spellID uint32) error // LoadPlayerTraits loads a player's selected traits LoadPlayerTraits(playerID uint32) (map[uint32]bool, error) // SavePlayerTraits saves a player's selected traits SavePlayerTraits(playerID uint32, selectedTraits map[uint32]bool) error } // Data structures used by the interfaces // Spell represents a spell in the game. type Spell interface { GetID() uint32 GetName() string GetIcon() uint32 GetIconBackdrop() uint32 GetTier() int8 } // Item represents an item in the game. type Item interface { GetID() uint32 GetName() string GetIcon() uint32 GetCount() int16 } // Player represents a player in the game. type Player interface { GetID() uint32 GetName() string GetLevel() int16 GetAdventureClass() int8 GetRace() int8 } // TraitSystemAdapter provides a high-level interface to the complete trait system. type TraitSystemAdapter struct { masterList *MasterTraitList traitManager *TraitManager packetBuilder TraitPacketBuilder spellManager SpellManager itemManager ItemManager playerManager PlayerManager packetManager PacketManager ruleManager RuleManager databaseService DatabaseService config *TraitSystemConfig } // NewTraitSystemAdapter creates a new trait system adapter with all dependencies. func NewTraitSystemAdapter( masterList *MasterTraitList, traitManager *TraitManager, packetBuilder TraitPacketBuilder, spellManager SpellManager, itemManager ItemManager, playerManager PlayerManager, packetManager PacketManager, ruleManager RuleManager, databaseService DatabaseService, ) *TraitSystemAdapter { config := &TraitSystemConfig{ TieringSelection: ruleManager.GetBool("Player", RuleTraitTieringSelection), UseClassicLevelTable: ruleManager.GetBool("Player", RuleClassicTraitLevelTable), FocusSelectLevel: ruleManager.GetInt32("Player", RuleTraitFocusSelectLevel), TrainingSelectLevel: ruleManager.GetInt32("Player", RuleTraitTrainingSelectLevel), RaceSelectLevel: ruleManager.GetInt32("Player", RuleTraitRaceSelectLevel), CharacterSelectLevel: ruleManager.GetInt32("Player", RuleTraitCharacterSelectLevel), } return &TraitSystemAdapter{ masterList: masterList, traitManager: traitManager, packetBuilder: packetBuilder, spellManager: spellManager, itemManager: itemManager, playerManager: playerManager, packetManager: packetManager, ruleManager: ruleManager, databaseService: databaseService, config: config, } } // Initialize sets up the trait system (loads traits from database, etc.). func (tsa *TraitSystemAdapter) Initialize() error { // Load traits from database err := tsa.databaseService.LoadTraits(tsa.masterList) if err != nil { return err } return nil } // GetTraitListPacket builds and returns the trait list packet for a player. func (tsa *TraitSystemAdapter) GetTraitListPacket(playerID uint32) ([]byte, error) { // Get player information player, err := tsa.playerManager.GetPlayer(playerID) if err != nil { return nil, err } // Get or create player trait state playerState := tsa.traitManager.GetPlayerState( playerID, player.GetLevel(), player.GetAdventureClass(), player.GetRace(), ) // Load player's selected traits if needed if len(playerState.SelectedTraits) == 0 { selectedTraits, err := tsa.databaseService.LoadPlayerTraits(playerID) if err == nil { playerState.SelectedTraits = selectedTraits } } // Generate trait lists if !tsa.masterList.GenerateTraitLists(playerState, playerState.Level, UnassignedGroupID) { return nil, fmt.Errorf("failed to generate trait lists for player %d", playerID) } // Get client version clientVersion, err := tsa.packetManager.GetClientVersion(playerID) if err != nil { clientVersion = 1200 // Default to a modern version } // Build packet data packetData, err := tsa.packetBuilder.BuildTraitListPacket(playerState, clientVersion) if err != nil { return nil, err } // Convert to actual packet bytes (this would be implemented by the packet system) // For now, return a placeholder return tsa.serializeTraitPacket(packetData, clientVersion) } // ChooseNextTrait handles automatic trait selection for level-up rewards. func (tsa *TraitSystemAdapter) ChooseNextTrait(playerID uint32) error { // Get player information player, err := tsa.playerManager.GetPlayer(playerID) if err != nil { return err } // Get player trait state playerState := tsa.traitManager.GetPlayerState( playerID, player.GetLevel(), player.GetAdventureClass(), player.GetRace(), ) // Load player's selected traits selectedTraits, err := tsa.databaseService.LoadPlayerTraits(playerID) if err == nil { playerState.SelectedTraits = selectedTraits } // Get available trait choices availableTraits, err := tsa.traitManager.GetAvailableTraits( playerID, player.GetLevel(), player.GetAdventureClass(), player.GetRace(), ) if err != nil { return err } if len(availableTraits) == 0 { return nil // No traits available for selection } // Determine packet type based on trait types packetType := tsa.determinePacketType(availableTraits, player.GetAdventureClass(), player.GetRace()) // Build trait reward packet rewardPacket, err := tsa.packetBuilder.BuildTraitRewardPacket(availableTraits, packetType) if err != nil { return err } // Send reward selection packet to player return tsa.sendTraitRewardPacket(playerID, rewardPacket) } // SelectTraits processes a player's trait selections. func (tsa *TraitSystemAdapter) SelectTraits(playerID uint32, selectedSpells []uint32) error { // Get player information player, err := tsa.playerManager.GetPlayer(playerID) if err != nil { return err } // Create selection request request := &TraitSelectionRequest{ PlayerID: playerID, TraitSpells: selectedSpells, PacketType: 0, // Will be determined based on traits } // Process the selection err = tsa.traitManager.SelectTraits( request, player.GetLevel(), player.GetAdventureClass(), player.GetRace(), ) if err != nil { return err } // Get updated player state playerState := tsa.traitManager.GetPlayerState( playerID, player.GetLevel(), player.GetAdventureClass(), player.GetRace(), ) // Save player's trait selections err = tsa.databaseService.SavePlayerTraits(playerID, playerState.SelectedTraits) if err != nil { return err } // Send confirmation message message := fmt.Sprintf("You have learned %d new trait(s).", len(selectedSpells)) return tsa.playerManager.SendMessageToPlayer(playerID, 4, message) // CHANNEL_NARRATIVE } // IsPlayerAllowedTrait checks if a player is allowed to select a specific trait. func (tsa *TraitSystemAdapter) IsPlayerAllowedTrait(playerID uint32, spellID uint32) (bool, error) { // Get trait trait := tsa.masterList.GetTrait(spellID) if trait == nil { return false, ErrTraitNotFound } // Get player information player, err := tsa.playerManager.GetPlayer(playerID) if err != nil { return false, err } // Get player trait state playerState := tsa.traitManager.GetPlayerState( playerID, player.GetLevel(), player.GetAdventureClass(), player.GetRace(), ) // Check if allowed return tsa.masterList.IsPlayerAllowedTrait(playerState, trait, tsa.config), nil } // GetPlayerTraitStats returns statistics about a player's trait selections. func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]any, error) { // Get player information player, err := tsa.playerManager.GetPlayer(playerID) if err != nil { return nil, err } // Get player trait state playerState := tsa.traitManager.GetPlayerState( playerID, player.GetLevel(), player.GetAdventureClass(), player.GetRace(), ) // Load selected traits if needed if len(playerState.SelectedTraits) == 0 { selectedTraits, err := tsa.databaseService.LoadPlayerTraits(playerID) if err == nil { playerState.SelectedTraits = selectedTraits } } // Generate trait lists for counting tsa.masterList.GenerateTraitLists(playerState, playerState.Level, UnassignedGroupID) // Count trait selections by type characterTraits := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.SortedTraitList, true) classTraining := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.ClassTraining, false) racialTraits := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.RaceTraits, false) + tsa.masterList.getSpellCount(playerState, playerState.TraitLists.InnateRaceTraits, false) focusEffects := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.FocusEffects, false) return map[string]any{ "player_id": playerID, "level": playerState.Level, "character_traits": characterTraits, "class_training": classTraining, "racial_traits": racialTraits, "focus_effects": focusEffects, "total_selected": len(playerState.SelectedTraits), "last_update": time.Now(), }, nil } // GetSystemStats returns comprehensive statistics about the trait system. func (tsa *TraitSystemAdapter) GetSystemStats() map[string]any { masterStats := tsa.masterList.GetStats() managerStats := tsa.traitManager.GetManagerStats() return map[string]any{ "total_traits": masterStats.TotalTraits, "traits_by_type": masterStats.TraitsByType, "traits_by_group": masterStats.TraitsByGroup, "traits_by_level": masterStats.TraitsByLevel, "players_with_traits": managerStats.PlayersWithTraits, "config": tsa.config, "last_update": time.Now(), } } // RefreshConfiguration reloads configuration from rules. func (tsa *TraitSystemAdapter) RefreshConfiguration() { tsa.config.TieringSelection = tsa.ruleManager.GetBool("Player", RuleTraitTieringSelection) tsa.config.UseClassicLevelTable = tsa.ruleManager.GetBool("Player", RuleClassicTraitLevelTable) tsa.config.FocusSelectLevel = tsa.ruleManager.GetInt32("Player", RuleTraitFocusSelectLevel) tsa.config.TrainingSelectLevel = tsa.ruleManager.GetInt32("Player", RuleTraitTrainingSelectLevel) tsa.config.RaceSelectLevel = tsa.ruleManager.GetInt32("Player", RuleTraitRaceSelectLevel) tsa.config.CharacterSelectLevel = tsa.ruleManager.GetInt32("Player", RuleTraitCharacterSelectLevel) // Update trait manager config tsa.traitManager.config = tsa.config } // ClearPlayerData removes cached data for a player (e.g., when they log out). func (tsa *TraitSystemAdapter) ClearPlayerData(playerID uint32) { tsa.traitManager.ClearPlayerState(playerID) } // AddTrait adds a new trait to the master list. func (tsa *TraitSystemAdapter) AddTrait(trait *TraitData) error { err := tsa.masterList.AddTrait(trait) if err != nil { return err } // Save to database return tsa.databaseService.SaveTrait(trait) } // RemoveTrait removes a trait from the master list. func (tsa *TraitSystemAdapter) RemoveTrait(spellID uint32) error { // Remove from database first err := tsa.databaseService.DeleteTrait(spellID) if err != nil { return err } // Reload traits from database to update master list tsa.masterList.DestroyTraits() return tsa.databaseService.LoadTraits(tsa.masterList) } // Helper methods // determinePacketType determines the appropriate packet type for a list of traits. func (tsa *TraitSystemAdapter) determinePacketType(traits []*TraitData, playerClass, playerRace int8) int8 { if len(traits) == 0 { return PacketTypeCharacterTrait } // Use the first trait to determine packet type trait := traits[0] if trait.IsUniversalTrait() { return PacketTypeCharacterTrait } if trait.ClassReq == playerClass && trait.IsTraining { return PacketTypeSpecializedTraining } if trait.RaceReq == playerRace { return PacketTypeRacialTradition } return PacketTypeEnemyMastery } // serializeTraitPacket converts trait packet data to byte array. func (tsa *TraitSystemAdapter) serializeTraitPacket(packetData *TraitPacketData, clientVersion int16) ([]byte, error) { // This would be implemented by the actual packet system // For now, return a placeholder indicating successful packet creation return []byte("TRAIT_PACKET_PLACEHOLDER"), nil } // sendTraitRewardPacket sends a trait reward packet to a player. func (tsa *TraitSystemAdapter) sendTraitRewardPacket(playerID uint32, rewardPacket *TraitRewardPacket) error { // This would serialize the reward packet and send it via the packet manager // For now, return success return nil } // TraitEventHandler handles trait-related events. type TraitEventHandler struct { adapter *TraitSystemAdapter } // NewTraitEventHandler creates a new trait event handler. func NewTraitEventHandler(adapter *TraitSystemAdapter) *TraitEventHandler { return &TraitEventHandler{ adapter: adapter, } } // OnPlayerLevelUp handles player level up events to check for new trait availability. func (teh *TraitEventHandler) OnPlayerLevelUp(playerID uint32, newLevel int16) error { // Check if player should get trait selection opportunity return teh.adapter.ChooseNextTrait(playerID) } // OnPlayerLogin handles player login events to refresh trait data. func (teh *TraitEventHandler) OnPlayerLogin(playerID uint32) error { // Refresh player's trait packet _, err := teh.adapter.GetTraitListPacket(playerID) return err } // OnPlayerLogout handles player logout events to clean up cached data. func (teh *TraitEventHandler) OnPlayerLogout(playerID uint32) { teh.adapter.ClearPlayerData(playerID) } // MockImplementations for testing // MockSpellManager is a mock implementation of SpellManager for testing. type MockSpellManager struct { spells map[uint32]MockSpell } // MockSpell is a mock implementation of Spell for testing. type MockSpell struct { id uint32 name string icon uint32 iconBackdrop uint32 tier int8 } func (ms MockSpell) GetID() uint32 { return ms.id } func (ms MockSpell) GetName() string { return ms.name } func (ms MockSpell) GetIcon() uint32 { return ms.icon } func (ms MockSpell) GetIconBackdrop() uint32 { return ms.iconBackdrop } func (ms MockSpell) GetTier() int8 { return ms.tier } func (msm *MockSpellManager) GetSpell(spellID uint32, tier int8) (Spell, error) { if spell, exists := msm.spells[spellID]; exists { return spell, nil } return nil, fmt.Errorf("spell not found: %d", spellID) } func (msm *MockSpellManager) GetPlayerSpells(playerID uint32) ([]uint32, error) { return []uint32{}, nil } func (msm *MockSpellManager) PlayerHasSpell(playerID uint32, spellID uint32, tier int8) (bool, error) { return false, nil } func (msm *MockSpellManager) GetSpellsBySkill(playerID uint32, skillID uint32) ([]uint32, error) { return []uint32{}, nil } // NewMockSpellManager creates a new mock spell manager. func NewMockSpellManager() *MockSpellManager { return &MockSpellManager{ spells: make(map[uint32]MockSpell), } } // AddMockSpell adds a mock spell for testing. func (msm *MockSpellManager) AddMockSpell(id uint32, name string, icon uint32, tier int8) { msm.spells[id] = MockSpell{ id: id, name: name, icon: icon, iconBackdrop: icon + 1000, tier: tier, } }