eq2go/internal/traits/interfaces.go

582 lines
18 KiB
Go

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,
}
}