convert more internals

This commit is contained in:
Sky Johnson 2025-07-31 11:22:03 -05:00
parent 47e6102af1
commit 812dd6716a
197 changed files with 26283 additions and 7557 deletions

5261
internal/Items.cpp Normal file

File diff suppressed because it is too large Load Diff

1298
internal/Items.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,16 +2,16 @@ package alt_advancement
// AA tab/group constants based on group # from DB
const (
AA_CLASS = 0 // Class-specific advancement trees
AA_SUBCLASS = 1 // Subclass-specific advancement trees
AA_SHADOW = 2 // Shadows advancement (from Shadows of Luclin)
AA_HEROIC = 3 // Heroic advancement (from Destiny of Velious)
AA_TRADESKILL = 4 // Tradeskill advancement trees
AA_PRESTIGE = 5 // Prestige advancement (from Destiny of Velious)
AA_TRADESKILL_PRESTIGE = 6 // Tradeskill prestige advancement
AA_DRAGON = 7 // Dragon advancement
AA_DRAGONCLASS = 8 // Dragon class-specific advancement
AA_FARSEAS = 9 // Far Seas advancement
AA_CLASS = 0 // Class-specific advancement trees
AA_SUBCLASS = 1 // Subclass-specific advancement trees
AA_SHADOW = 2 // Shadows advancement (from Shadows of Luclin)
AA_HEROIC = 3 // Heroic advancement (from Destiny of Velious)
AA_TRADESKILL = 4 // Tradeskill advancement trees
AA_PRESTIGE = 5 // Prestige advancement (from Destiny of Velious)
AA_TRADESKILL_PRESTIGE = 6 // Tradeskill prestige advancement
AA_DRAGON = 7 // Dragon advancement
AA_DRAGONCLASS = 8 // Dragon class-specific advancement
AA_FARSEAS = 9 // Far Seas advancement
)
// AA tab names for display
@ -30,28 +30,28 @@ var AATabNames = map[int8]string{
// Maximum AA values per tab (from C++ packet data)
const (
MAX_CLASS_AA = 100 // 0x64
MAX_SUBCLASS_AA = 100 // 0x64
MAX_SHADOWS_AA = 70 // 0x46
MAX_HEROIC_AA = 50 // 0x32
MAX_TRADESKILL_AA = 40 // 0x28
MAX_PRESTIGE_AA = 25 // 0x19
MAX_TRADESKILL_PRESTIGE_AA = 25 // 0x19
MAX_DRAGON_AA = 100 // Estimated
MAX_DRAGONCLASS_AA = 100 // Estimated
MAX_FARSEAS_AA = 100 // Estimated
MAX_CLASS_AA = 100 // 0x64
MAX_SUBCLASS_AA = 100 // 0x64
MAX_SHADOWS_AA = 70 // 0x46
MAX_HEROIC_AA = 50 // 0x32
MAX_TRADESKILL_AA = 40 // 0x28
MAX_PRESTIGE_AA = 25 // 0x19
MAX_TRADESKILL_PRESTIGE_AA = 25 // 0x19
MAX_DRAGON_AA = 100 // Estimated
MAX_DRAGONCLASS_AA = 100 // Estimated
MAX_FARSEAS_AA = 100 // Estimated
)
// AA template constants
const (
AA_TEMPLATE_PERSONAL_1 = 1 // Personal template 1
AA_TEMPLATE_PERSONAL_2 = 2 // Personal template 2
AA_TEMPLATE_PERSONAL_3 = 3 // Personal template 3
AA_TEMPLATE_SERVER_1 = 4 // Server template 1
AA_TEMPLATE_SERVER_2 = 5 // Server template 2
AA_TEMPLATE_SERVER_3 = 6 // Server template 3
AA_TEMPLATE_CURRENT = 7 // Current active template
MAX_AA_TEMPLATES = 8 // Maximum number of templates
AA_TEMPLATE_PERSONAL_1 = 1 // Personal template 1
AA_TEMPLATE_PERSONAL_2 = 2 // Personal template 2
AA_TEMPLATE_PERSONAL_3 = 3 // Personal template 3
AA_TEMPLATE_SERVER_1 = 4 // Server template 1
AA_TEMPLATE_SERVER_2 = 5 // Server template 2
AA_TEMPLATE_SERVER_3 = 6 // Server template 3
AA_TEMPLATE_CURRENT = 7 // Current active template
MAX_AA_TEMPLATES = 8 // Maximum number of templates
)
// AA template names
@ -67,98 +67,98 @@ var AATemplateNames = map[int8]string{
// AA prerequisite constants
const (
AA_PREREQ_NONE = 0 // No prerequisite
AA_PREREQ_EXPANSION = 1 // Requires specific expansion
AA_PREREQ_LEVEL = 2 // Requires minimum level
AA_PREREQ_CLASS = 3 // Requires specific class
AA_PREREQ_POINTS = 4 // Requires points spent in tree
AA_PREREQ_ACHIEVEMENT = 5 // Requires achievement completion
AA_PREREQ_NONE = 0 // No prerequisite
AA_PREREQ_EXPANSION = 1 // Requires specific expansion
AA_PREREQ_LEVEL = 2 // Requires minimum level
AA_PREREQ_CLASS = 3 // Requires specific class
AA_PREREQ_POINTS = 4 // Requires points spent in tree
AA_PREREQ_ACHIEVEMENT = 5 // Requires achievement completion
)
// Expansion requirement flags
const (
EXPANSION_NONE = 0x00 // No expansion required
EXPANSION_KOS = 0x01 // Kingdom of Sky required
EXPANSION_EOF = 0x02 // Echoes of Faydwer required
EXPANSION_ROK = 0x04 // Rise of Kunark required
EXPANSION_TSO = 0x08 // The Shadow Odyssey required
EXPANSION_SF = 0x10 // Sentinel's Fate required
EXPANSION_DOV = 0x20 // Destiny of Velious required
EXPANSION_COE = 0x40 // Chains of Eternity required
EXPANSION_TOV = 0x80 // Tears of Veeshan required
EXPANSION_NONE = 0x00 // No expansion required
EXPANSION_KOS = 0x01 // Kingdom of Sky required
EXPANSION_EOF = 0x02 // Echoes of Faydwer required
EXPANSION_ROK = 0x04 // Rise of Kunark required
EXPANSION_TSO = 0x08 // The Shadow Odyssey required
EXPANSION_SF = 0x10 // Sentinel's Fate required
EXPANSION_DOV = 0x20 // Destiny of Velious required
EXPANSION_COE = 0x40 // Chains of Eternity required
EXPANSION_TOV = 0x80 // Tears of Veeshan required
)
// AA node positioning constants
const (
MIN_AA_COL = 0 // Minimum column position
MAX_AA_COL = 10 // Maximum column position
MIN_AA_ROW = 0 // Minimum row position
MAX_AA_ROW = 15 // Maximum row position
MIN_AA_COL = 0 // Minimum column position
MAX_AA_COL = 10 // Maximum column position
MIN_AA_ROW = 0 // Minimum row position
MAX_AA_ROW = 15 // Maximum row position
)
// AA cost and rank constants
const (
MIN_RANK_COST = 1 // Minimum cost per rank
MAX_RANK_COST = 10 // Maximum cost per rank
MIN_MAX_RANK = 1 // Minimum maximum rank
MAX_MAX_RANK = 20 // Maximum maximum rank
MIN_TITLE_LEVEL = 1 // Minimum title level
MAX_TITLE_LEVEL = 100 // Maximum title level
MIN_RANK_COST = 1 // Minimum cost per rank
MAX_RANK_COST = 10 // Maximum cost per rank
MIN_MAX_RANK = 1 // Minimum maximum rank
MAX_MAX_RANK = 20 // Maximum maximum rank
MIN_TITLE_LEVEL = 1 // Minimum title level
MAX_TITLE_LEVEL = 100 // Maximum title level
)
// AA packet operation codes
const (
OP_ADVENTURE_LIST = 0x023B // Adventure list packet opcode
OP_AA_UPDATE = 0x024C // AA update packet opcode
OP_AA_PURCHASE = 0x024D // AA purchase packet opcode
OP_ADVENTURE_LIST = 0x023B // Adventure list packet opcode
OP_AA_UPDATE = 0x024C // AA update packet opcode
OP_AA_PURCHASE = 0x024D // AA purchase packet opcode
)
// AA display modes
const (
AA_DISPLAY_NEW = 0 // New template display
AA_DISPLAY_CHANGE = 1 // Change template display
AA_DISPLAY_UPDATE = 2 // Update existing display
AA_DISPLAY_NEW = 0 // New template display
AA_DISPLAY_CHANGE = 1 // Change template display
AA_DISPLAY_UPDATE = 2 // Update existing display
)
// AA validation constants
const (
MIN_SPELL_ID = 1 // Minimum valid spell ID
MAX_SPELL_ID = 2147483647 // Maximum valid spell ID
MIN_NODE_ID = 1 // Minimum valid node ID
MAX_NODE_ID = 2147483647 // Maximum valid node ID
MIN_SPELL_ID = 1 // Minimum valid spell ID
MAX_SPELL_ID = 2147483647 // Maximum valid spell ID
MIN_NODE_ID = 1 // Minimum valid node ID
MAX_NODE_ID = 2147483647 // Maximum valid node ID
)
// AA processing constants
const (
AA_PROCESSING_BATCH_SIZE = 100 // Batch size for processing AAs
AA_CACHE_SIZE = 10000 // Cache size for AA data
AA_UPDATE_INTERVAL = 1000 // Update interval in milliseconds
AA_PROCESSING_BATCH_SIZE = 100 // Batch size for processing AAs
AA_CACHE_SIZE = 10000 // Cache size for AA data
AA_UPDATE_INTERVAL = 1000 // Update interval in milliseconds
)
// AA error codes
const (
AA_ERROR_NONE = 0 // No error
AA_ERROR_INVALID_SPELL_ID = 1 // Invalid spell ID
AA_ERROR_INVALID_NODE_ID = 2 // Invalid node ID
AA_ERROR_INSUFFICIENT_POINTS = 3 // Insufficient AA points
AA_ERROR_PREREQ_NOT_MET = 4 // Prerequisites not met
AA_ERROR_MAX_RANK_REACHED = 5 // Maximum rank already reached
AA_ERROR_INVALID_CLASS = 6 // Invalid class for this AA
AA_ERROR_EXPANSION_REQUIRED = 7 // Required expansion not owned
AA_ERROR_LEVEL_TOO_LOW = 8 // Character level too low
AA_ERROR_TREE_LOCKED = 9 // AA tree is locked
AA_ERROR_DATABASE_ERROR = 10 // Database operation failed
AA_ERROR_NONE = 0 // No error
AA_ERROR_INVALID_SPELL_ID = 1 // Invalid spell ID
AA_ERROR_INVALID_NODE_ID = 2 // Invalid node ID
AA_ERROR_INSUFFICIENT_POINTS = 3 // Insufficient AA points
AA_ERROR_PREREQ_NOT_MET = 4 // Prerequisites not met
AA_ERROR_MAX_RANK_REACHED = 5 // Maximum rank already reached
AA_ERROR_INVALID_CLASS = 6 // Invalid class for this AA
AA_ERROR_EXPANSION_REQUIRED = 7 // Required expansion not owned
AA_ERROR_LEVEL_TOO_LOW = 8 // Character level too low
AA_ERROR_TREE_LOCKED = 9 // AA tree is locked
AA_ERROR_DATABASE_ERROR = 10 // Database operation failed
)
// AA statistic tracking constants
const (
STAT_TOTAL_AAS_LOADED = "total_aas_loaded"
STAT_TOTAL_NODES_LOADED = "total_nodes_loaded"
STAT_AAS_PER_TAB = "aas_per_tab"
STAT_PLAYER_AA_PURCHASES = "player_aa_purchases"
STAT_CACHE_HITS = "cache_hits"
STAT_CACHE_MISSES = "cache_misses"
STAT_DATABASE_QUERIES = "database_queries"
STAT_TOTAL_AAS_LOADED = "total_aas_loaded"
STAT_TOTAL_NODES_LOADED = "total_nodes_loaded"
STAT_AAS_PER_TAB = "aas_per_tab"
STAT_PLAYER_AA_PURCHASES = "player_aa_purchases"
STAT_CACHE_HITS = "cache_hits"
STAT_CACHE_MISSES = "cache_misses"
STAT_DATABASE_QUERIES = "database_queries"
)
// Default AA configuration values

View File

@ -3,6 +3,7 @@ package alt_advancement
import (
"database/sql"
"log"
"sync"
"time"
)
@ -273,7 +274,7 @@ type AAAware interface {
// AAAdapter adapts AA functionality for other systems
type AAAdapter struct {
manager AAManagerInterface
manager AAManagerInterface
characterID int32
}
@ -583,12 +584,12 @@ func (c *SimpleAACache) GetStats() map[string]interface{} {
defer c.mutex.RUnlock()
return map[string]interface{}{
"hits": c.hits,
"misses": c.misses,
"aa_data_count": len(c.aaData),
"player_count": len(c.playerStates),
"tree_node_count": len(c.treeNodes),
"max_size": c.maxSize,
"hits": c.hits,
"misses": c.misses,
"aa_data_count": len(c.aaData),
"player_count": len(c.playerStates),
"tree_node_count": len(c.treeNodes),
"max_size": c.maxSize,
}
}

View File

@ -2,7 +2,6 @@ package alt_advancement
import (
"fmt"
"sync"
"time"
)
@ -534,14 +533,14 @@ func (am *AAManager) performAAPurchase(playerState *AAPlayerState, aaData *AltAd
progress := playerState.AAProgress[aaData.NodeID]
if progress == nil {
progress = &PlayerAAData{
CharacterID: playerState.CharacterID,
NodeID: aaData.NodeID,
CurrentRank: 0,
PointsSpent: 0,
TemplateID: playerState.ActiveTemplate,
TabID: aaData.Group,
PurchasedAt: time.Now(),
UpdatedAt: time.Now(),
CharacterID: playerState.CharacterID,
NodeID: aaData.NodeID,
CurrentRank: 0,
PointsSpent: 0,
TemplateID: playerState.ActiveTemplate,
TabID: aaData.Group,
PurchasedAt: time.Now(),
UpdatedAt: time.Now(),
}
playerState.AAProgress[aaData.NodeID] = progress
}

View File

@ -3,7 +3,6 @@ package alt_advancement
import (
"fmt"
"sort"
"sync"
"time"
)

View File

@ -8,287 +8,287 @@ import (
// AltAdvanceData represents an Alternate Advancement node
type AltAdvanceData struct {
// Core identification
SpellID int32 `json:"spell_id" db:"spell_id"`
NodeID int32 `json:"node_id" db:"node_id"`
SpellCRC int32 `json:"spell_crc" db:"spell_crc"`
SpellID int32 `json:"spell_id" db:"spell_id"`
NodeID int32 `json:"node_id" db:"node_id"`
SpellCRC int32 `json:"spell_crc" db:"spell_crc"`
// Display information
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
// Tree organization
Group int8 `json:"group" db:"group"` // AA tab (AA_CLASS, AA_SUBCLASS, etc.)
Col int8 `json:"col" db:"col"` // Column position in tree
Row int8 `json:"row" db:"row"` // Row position in tree
Group int8 `json:"group" db:"group"` // AA tab (AA_CLASS, AA_SUBCLASS, etc.)
Col int8 `json:"col" db:"col"` // Column position in tree
Row int8 `json:"row" db:"row"` // Row position in tree
// Visual representation
Icon int16 `json:"icon" db:"icon"` // Primary icon ID
Icon2 int16 `json:"icon2" db:"icon2"` // Secondary icon ID
Icon int16 `json:"icon" db:"icon"` // Primary icon ID
Icon2 int16 `json:"icon2" db:"icon2"` // Secondary icon ID
// Ranking system
RankCost int8 `json:"rank_cost" db:"rank_cost"` // Cost per rank
MaxRank int8 `json:"max_rank" db:"max_rank"` // Maximum achievable rank
RankCost int8 `json:"rank_cost" db:"rank_cost"` // Cost per rank
MaxRank int8 `json:"max_rank" db:"max_rank"` // Maximum achievable rank
// Prerequisites
MinLevel int8 `json:"min_level" db:"min_level"` // Minimum character level
RankPrereqID int32 `json:"rank_prereq_id" db:"rank_prereq_id"` // Prerequisite AA node ID
RankPrereq int8 `json:"rank_prereq" db:"rank_prereq"` // Required rank in prerequisite
ClassReq int8 `json:"class_req" db:"class_req"` // Required class
Tier int8 `json:"tier" db:"tier"` // AA tier
ReqPoints int8 `json:"req_points" db:"req_points"` // Required points in classification
ReqTreePoints int16 `json:"req_tree_points" db:"req_tree_points"` // Required points in entire tree
MinLevel int8 `json:"min_level" db:"min_level"` // Minimum character level
RankPrereqID int32 `json:"rank_prereq_id" db:"rank_prereq_id"` // Prerequisite AA node ID
RankPrereq int8 `json:"rank_prereq" db:"rank_prereq"` // Required rank in prerequisite
ClassReq int8 `json:"class_req" db:"class_req"` // Required class
Tier int8 `json:"tier" db:"tier"` // AA tier
ReqPoints int8 `json:"req_points" db:"req_points"` // Required points in classification
ReqTreePoints int16 `json:"req_tree_points" db:"req_tree_points"` // Required points in entire tree
// Display classification
ClassName string `json:"class_name" db:"class_name"` // Class name for display
SubclassName string `json:"subclass_name" db:"subclass_name"` // Subclass name for display
LineTitle string `json:"line_title" db:"line_title"` // AA line title
TitleLevel int8 `json:"title_level" db:"title_level"` // Title level requirement
ClassName string `json:"class_name" db:"class_name"` // Class name for display
SubclassName string `json:"subclass_name" db:"subclass_name"` // Subclass name for display
LineTitle string `json:"line_title" db:"line_title"` // AA line title
TitleLevel int8 `json:"title_level" db:"title_level"` // Title level requirement
// Metadata
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// TreeNodeData represents class-specific AA tree node configuration
type TreeNodeData struct {
ClassID int32 `json:"class_id" db:"class_id"` // Character class ID
TreeID int32 `json:"tree_id" db:"tree_id"` // Tree node identifier
AATreeID int32 `json:"aa_tree_id" db:"aa_tree_id"` // AA tree classification ID
ClassID int32 `json:"class_id" db:"class_id"` // Character class ID
TreeID int32 `json:"tree_id" db:"tree_id"` // Tree node identifier
AATreeID int32 `json:"aa_tree_id" db:"aa_tree_id"` // AA tree classification ID
}
// AAEntry represents a player's AA entry in a template
type AAEntry struct {
TemplateID int8 `json:"template_id" db:"template_id"` // Template identifier (1-8)
TabID int8 `json:"tab_id" db:"tab_id"` // Tab identifier
AAID int32 `json:"aa_id" db:"aa_id"` // AA node ID
Order int16 `json:"order" db:"order"` // Display order
TreeID int8 `json:"tree_id" db:"tree_id"` // Tree identifier
TemplateID int8 `json:"template_id" db:"template_id"` // Template identifier (1-8)
TabID int8 `json:"tab_id" db:"tab_id"` // Tab identifier
AAID int32 `json:"aa_id" db:"aa_id"` // AA node ID
Order int16 `json:"order" db:"order"` // Display order
TreeID int8 `json:"tree_id" db:"tree_id"` // Tree identifier
}
// PlayerAAData represents a player's AA progression
type PlayerAAData struct {
// Player identification
CharacterID int32 `json:"character_id" db:"character_id"`
CharacterID int32 `json:"character_id" db:"character_id"`
// AA progression
NodeID int32 `json:"node_id" db:"node_id"` // AA node ID
CurrentRank int8 `json:"current_rank" db:"current_rank"` // Current rank in this AA
PointsSpent int32 `json:"points_spent" db:"points_spent"` // Total points spent on this AA
NodeID int32 `json:"node_id" db:"node_id"` // AA node ID
CurrentRank int8 `json:"current_rank" db:"current_rank"` // Current rank in this AA
PointsSpent int32 `json:"points_spent" db:"points_spent"` // Total points spent on this AA
// Template assignment
TemplateID int8 `json:"template_id" db:"template_id"` // Template this AA belongs to
TabID int8 `json:"tab_id" db:"tab_id"` // Tab this AA belongs to
Order int16 `json:"order" db:"order"` // Display order
TemplateID int8 `json:"template_id" db:"template_id"` // Template this AA belongs to
TabID int8 `json:"tab_id" db:"tab_id"` // Tab this AA belongs to
Order int16 `json:"order" db:"order"` // Display order
// Timestamps
PurchasedAt time.Time `json:"purchased_at" db:"purchased_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
PurchasedAt time.Time `json:"purchased_at" db:"purchased_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// AATemplate represents an AA template configuration
type AATemplate struct {
// Template identification
TemplateID int8 `json:"template_id"`
Name string `json:"name"`
Description string `json:"description"`
IsPersonal bool `json:"is_personal"` // True for personal templates (1-3)
IsServer bool `json:"is_server"` // True for server templates (4-6)
IsCurrent bool `json:"is_current"` // True for current active template
TemplateID int8 `json:"template_id"`
Name string `json:"name"`
Description string `json:"description"`
IsPersonal bool `json:"is_personal"` // True for personal templates (1-3)
IsServer bool `json:"is_server"` // True for server templates (4-6)
IsCurrent bool `json:"is_current"` // True for current active template
// Template data
Entries []*AAEntry `json:"entries"` // AA entries in this template
Entries []*AAEntry `json:"entries"` // AA entries in this template
// Metadata
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// AATab represents an AA tab with its associated data
type AATab struct {
// Tab identification
TabID int8 `json:"tab_id"`
Group int8 `json:"group"`
Name string `json:"name"`
TabID int8 `json:"tab_id"`
Group int8 `json:"group"`
Name string `json:"name"`
// Tab configuration
MaxAA int32 `json:"max_aa"` // Maximum AA points for this tab
ClassID int32 `json:"class_id"` // Associated class ID
ExpansionReq int8 `json:"expansion_req"` // Required expansion flags
MaxAA int32 `json:"max_aa"` // Maximum AA points for this tab
ClassID int32 `json:"class_id"` // Associated class ID
ExpansionReq int8 `json:"expansion_req"` // Required expansion flags
// Current state
PointsSpent int32 `json:"points_spent"` // Points spent in this tab
PointsSpent int32 `json:"points_spent"` // Points spent in this tab
PointsAvailable int32 `json:"points_available"` // Available points for spending
// AA nodes in this tab
Nodes []*AltAdvanceData `json:"nodes"`
Nodes []*AltAdvanceData `json:"nodes"`
// Metadata
LastUpdate time.Time `json:"last_update"`
LastUpdate time.Time `json:"last_update"`
}
// AAPlayerState represents a player's complete AA state
type AAPlayerState struct {
// Player identification
CharacterID int32 `json:"character_id"`
CharacterID int32 `json:"character_id"`
// AA points
TotalPoints int32 `json:"total_points"` // Total AA points earned
SpentPoints int32 `json:"spent_points"` // Total AA points spent
TotalPoints int32 `json:"total_points"` // Total AA points earned
SpentPoints int32 `json:"spent_points"` // Total AA points spent
AvailablePoints int32 `json:"available_points"` // Available AA points
BankedPoints int32 `json:"banked_points"` // Banked AA points
BankedPoints int32 `json:"banked_points"` // Banked AA points
// Templates
ActiveTemplate int8 `json:"active_template"` // Currently active template
Templates map[int8]*AATemplate `json:"templates"` // All templates
ActiveTemplate int8 `json:"active_template"` // Currently active template
Templates map[int8]*AATemplate `json:"templates"` // All templates
// Tab states
Tabs map[int8]*AATab `json:"tabs"` // Tab states
Tabs map[int8]*AATab `json:"tabs"` // Tab states
// Player AA progression
AAProgress map[int32]*PlayerAAData `json:"aa_progress"` // AA node progress by node ID
AAProgress map[int32]*PlayerAAData `json:"aa_progress"` // AA node progress by node ID
// Caching and synchronization
mutex sync.RWMutex `json:"-"`
lastUpdate time.Time `json:"last_update"`
needsSync bool `json:"-"`
mutex sync.RWMutex `json:"-"`
lastUpdate time.Time `json:"last_update"`
needsSync bool `json:"-"`
}
// MasterAAList manages all AA definitions
type MasterAAList struct {
// AA storage
aaList []*AltAdvanceData `json:"aa_list"`
aaBySpellID map[int32]*AltAdvanceData `json:"-"` // Fast lookup by spell ID
aaByNodeID map[int32]*AltAdvanceData `json:"-"` // Fast lookup by node ID
aaByGroup map[int8][]*AltAdvanceData `json:"-"` // Fast lookup by group/tab
aaList []*AltAdvanceData `json:"aa_list"`
aaBySpellID map[int32]*AltAdvanceData `json:"-"` // Fast lookup by spell ID
aaByNodeID map[int32]*AltAdvanceData `json:"-"` // Fast lookup by node ID
aaByGroup map[int8][]*AltAdvanceData `json:"-"` // Fast lookup by group/tab
// Synchronization
mutex sync.RWMutex `json:"-"`
mutex sync.RWMutex `json:"-"`
// Statistics
totalLoaded int64 `json:"total_loaded"`
lastLoadTime time.Time `json:"last_load_time"`
totalLoaded int64 `json:"total_loaded"`
lastLoadTime time.Time `json:"last_load_time"`
}
// MasterAANodeList manages tree node configurations
type MasterAANodeList struct {
// Node storage
nodeList []*TreeNodeData `json:"node_list"`
nodesByClass map[int32][]*TreeNodeData `json:"-"` // Fast lookup by class ID
nodesByTree map[int32]*TreeNodeData `json:"-"` // Fast lookup by tree ID
nodeList []*TreeNodeData `json:"node_list"`
nodesByClass map[int32][]*TreeNodeData `json:"-"` // Fast lookup by class ID
nodesByTree map[int32]*TreeNodeData `json:"-"` // Fast lookup by tree ID
// Synchronization
mutex sync.RWMutex `json:"-"`
mutex sync.RWMutex `json:"-"`
// Statistics
totalLoaded int64 `json:"total_loaded"`
lastLoadTime time.Time `json:"last_load_time"`
totalLoaded int64 `json:"total_loaded"`
lastLoadTime time.Time `json:"last_load_time"`
}
// AAManager manages the entire AA system
type AAManager struct {
// Core lists
masterAAList *MasterAAList `json:"master_aa_list"`
masterNodeList *MasterAANodeList `json:"master_node_list"`
masterAAList *MasterAAList `json:"master_aa_list"`
masterNodeList *MasterAANodeList `json:"master_node_list"`
// Player states
playerStates map[int32]*AAPlayerState `json:"-"` // Player AA states by character ID
statesMutex sync.RWMutex `json:"-"`
playerStates map[int32]*AAPlayerState `json:"-"` // Player AA states by character ID
statesMutex sync.RWMutex `json:"-"`
// Configuration
config AAManagerConfig `json:"config"`
config AAManagerConfig `json:"config"`
// Database interface
database AADatabase `json:"-"`
database AADatabase `json:"-"`
// Packet handler
packetHandler AAPacketHandler `json:"-"`
packetHandler AAPacketHandler `json:"-"`
// Event handlers
eventHandlers []AAEventHandler `json:"-"`
eventMutex sync.RWMutex `json:"-"`
eventHandlers []AAEventHandler `json:"-"`
eventMutex sync.RWMutex `json:"-"`
// Statistics
stats AAManagerStats `json:"stats"`
statsMutex sync.RWMutex `json:"-"`
stats AAManagerStats `json:"stats"`
statsMutex sync.RWMutex `json:"-"`
// Background processing
stopChan chan struct{} `json:"-"`
wg sync.WaitGroup `json:"-"`
stopChan chan struct{} `json:"-"`
wg sync.WaitGroup `json:"-"`
}
// AAManagerConfig holds configuration for the AA manager
type AAManagerConfig struct {
// System settings
EnableAASystem bool `json:"enable_aa_system"`
EnableCaching bool `json:"enable_caching"`
EnableValidation bool `json:"enable_validation"`
EnableLogging bool `json:"enable_logging"`
EnableAASystem bool `json:"enable_aa_system"`
EnableCaching bool `json:"enable_caching"`
EnableValidation bool `json:"enable_validation"`
EnableLogging bool `json:"enable_logging"`
// Player settings
AAPointsPerLevel int32 `json:"aa_points_per_level"`
MaxBankedPoints int32 `json:"max_banked_points"`
EnableAABanking bool `json:"enable_aa_banking"`
AAPointsPerLevel int32 `json:"aa_points_per_level"`
MaxBankedPoints int32 `json:"max_banked_points"`
EnableAABanking bool `json:"enable_aa_banking"`
// Performance settings
CacheSize int32 `json:"cache_size"`
UpdateInterval time.Duration `json:"update_interval"`
BatchSize int32 `json:"batch_size"`
CacheSize int32 `json:"cache_size"`
UpdateInterval time.Duration `json:"update_interval"`
BatchSize int32 `json:"batch_size"`
// Database settings
DatabaseEnabled bool `json:"database_enabled"`
AutoSave bool `json:"auto_save"`
SaveInterval time.Duration `json:"save_interval"`
DatabaseEnabled bool `json:"database_enabled"`
AutoSave bool `json:"auto_save"`
SaveInterval time.Duration `json:"save_interval"`
}
// AAManagerStats holds statistics about the AA system
type AAManagerStats struct {
// Loading statistics
TotalAAsLoaded int64 `json:"total_aas_loaded"`
TotalNodesLoaded int64 `json:"total_nodes_loaded"`
LastLoadTime time.Time `json:"last_load_time"`
LoadDuration time.Duration `json:"load_duration"`
TotalAAsLoaded int64 `json:"total_aas_loaded"`
TotalNodesLoaded int64 `json:"total_nodes_loaded"`
LastLoadTime time.Time `json:"last_load_time"`
LoadDuration time.Duration `json:"load_duration"`
// Player statistics
ActivePlayers int64 `json:"active_players"`
TotalAAPurchases int64 `json:"total_aa_purchases"`
TotalPointsSpent int64 `json:"total_points_spent"`
AveragePointsSpent float64 `json:"average_points_spent"`
ActivePlayers int64 `json:"active_players"`
TotalAAPurchases int64 `json:"total_aa_purchases"`
TotalPointsSpent int64 `json:"total_points_spent"`
AveragePointsSpent float64 `json:"average_points_spent"`
// Performance statistics
CacheHits int64 `json:"cache_hits"`
CacheMisses int64 `json:"cache_misses"`
DatabaseQueries int64 `json:"database_queries"`
PacketsSent int64 `json:"packets_sent"`
CacheHits int64 `json:"cache_hits"`
CacheMisses int64 `json:"cache_misses"`
DatabaseQueries int64 `json:"database_queries"`
PacketsSent int64 `json:"packets_sent"`
// Tab usage statistics
TabUsage map[int8]int64 `json:"tab_usage"`
PopularAAs map[int32]int64 `json:"popular_aas"`
TabUsage map[int8]int64 `json:"tab_usage"`
PopularAAs map[int32]int64 `json:"popular_aas"`
// Error statistics
ValidationErrors int64 `json:"validation_errors"`
DatabaseErrors int64 `json:"database_errors"`
PacketErrors int64 `json:"packet_errors"`
ValidationErrors int64 `json:"validation_errors"`
DatabaseErrors int64 `json:"database_errors"`
PacketErrors int64 `json:"packet_errors"`
// Timing statistics
LastStatsUpdate time.Time `json:"last_stats_update"`
LastStatsUpdate time.Time `json:"last_stats_update"`
}
// DefaultAAManagerConfig returns a default configuration
func DefaultAAManagerConfig() AAManagerConfig {
return AAManagerConfig{
EnableAASystem: DEFAULT_ENABLE_AA_SYSTEM,
EnableCaching: DEFAULT_ENABLE_AA_CACHING,
EnableValidation: DEFAULT_ENABLE_AA_VALIDATION,
EnableLogging: DEFAULT_ENABLE_AA_LOGGING,
AAPointsPerLevel: DEFAULT_AA_POINTS_PER_LEVEL,
MaxBankedPoints: DEFAULT_AA_MAX_BANKED_POINTS,
EnableAABanking: true,
CacheSize: AA_CACHE_SIZE,
UpdateInterval: time.Duration(AA_UPDATE_INTERVAL) * time.Millisecond,
BatchSize: AA_PROCESSING_BATCH_SIZE,
DatabaseEnabled: true,
AutoSave: true,
SaveInterval: 5 * time.Minute,
EnableAASystem: DEFAULT_ENABLE_AA_SYSTEM,
EnableCaching: DEFAULT_ENABLE_AA_CACHING,
EnableValidation: DEFAULT_ENABLE_AA_VALIDATION,
EnableLogging: DEFAULT_ENABLE_AA_LOGGING,
AAPointsPerLevel: DEFAULT_AA_POINTS_PER_LEVEL,
MaxBankedPoints: DEFAULT_AA_MAX_BANKED_POINTS,
EnableAABanking: true,
CacheSize: AA_CACHE_SIZE,
UpdateInterval: time.Duration(AA_UPDATE_INTERVAL) * time.Millisecond,
BatchSize: AA_PROCESSING_BATCH_SIZE,
DatabaseEnabled: true,
AutoSave: true,
SaveInterval: 5 * time.Minute,
}
}
@ -349,10 +349,10 @@ func (aad *AltAdvanceData) Copy() *AltAdvanceData {
// IsValid validates the AltAdvanceData
func (aad *AltAdvanceData) IsValid() bool {
return aad.SpellID > 0 &&
aad.NodeID > 0 &&
len(aad.Name) > 0 &&
aad.MaxRank > 0 &&
aad.RankCost > 0
aad.NodeID > 0 &&
len(aad.Name) > 0 &&
aad.MaxRank > 0 &&
aad.RankCost > 0
}
// GetMaxAAForTab returns the maximum AA points for a given tab

View File

@ -13,11 +13,11 @@ type Manager struct {
mutex sync.RWMutex
// Statistics
totalLookups int64
successfulLookups int64
failedLookups int64
cacheHits int64
cacheMisses int64
totalLookups int64
successfulLookups int64
failedLookups int64
cacheHits int64
cacheMisses int64
}
// NewManager creates a new appearance manager

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"strings"
"sync"
"time"
)

View File

@ -21,14 +21,14 @@ type Channel struct {
// ChannelMessage represents a message sent to a channel
type ChannelMessage struct {
SenderID int32
SenderName string
Message string
LanguageID int32
ChannelName string
Timestamp time.Time
IsEmote bool
IsOOC bool
SenderID int32
SenderName string
Message string
LanguageID int32
ChannelName string
Timestamp time.Time
IsEmote bool
IsOOC bool
}
// ChannelMember represents a member in a channel
@ -68,19 +68,19 @@ type ChatManager struct {
database ChannelDatabase
// Integration interfaces
clientManager ClientManager
playerManager PlayerManager
clientManager ClientManager
playerManager PlayerManager
languageProcessor LanguageProcessor
}
// ChatStatistics provides statistics about chat system usage
type ChatStatistics struct {
TotalChannels int
WorldChannels int
CustomChannels int
TotalMembers int
MessagesPerHour int
ActiveChannels int
TotalChannels int
WorldChannels int
CustomChannels int
TotalMembers int
MessagesPerHour int
ActiveChannels int
}
// ChannelFilter provides filtering options for channel lists

View File

@ -10,15 +10,15 @@ const (
ClassScout = 31
// Fighter subclasses
ClassWarrior = 2
ClassGuardian = 3
ClassBerserker = 4
ClassBrawler = 5
ClassMonk = 6
ClassBruiser = 7
ClassCrusader = 8
ClassWarrior = 2
ClassGuardian = 3
ClassBerserker = 4
ClassBrawler = 5
ClassMonk = 6
ClassBruiser = 7
ClassCrusader = 8
ClassShadowknight = 9
ClassPaladin = 10
ClassPaladin = 10
// Priest subclasses
ClassCleric = 12
@ -43,17 +43,17 @@ const (
ClassNecromancer = 30
// Scout subclasses
ClassRogue = 32
ClassRogue = 32
ClassSwashbuckler = 33
ClassBrigand = 34
ClassBard = 35
ClassTroubador = 36
ClassDirge = 37
ClassPredator = 38
ClassRanger = 39
ClassAssassin = 40
ClassAnimalist = 41
ClassBeastlord = 42
ClassBrigand = 34
ClassBard = 35
ClassTroubador = 36
ClassDirge = 37
ClassPredator = 38
ClassRanger = 39
ClassAssassin = 40
ClassAnimalist = 41
ClassBeastlord = 42
// Special classes
ClassShaper = 43

View File

@ -1,7 +1,6 @@
package classes
import (
"fmt"
"math/rand"
"strings"
)

View File

@ -11,9 +11,9 @@ import (
func NewCollection() *Collection {
return &Collection{
collectionItems: make([]CollectionItem, 0),
rewardItems: make([]CollectionRewardItem, 0),
rewardItems: make([]CollectionRewardItem, 0),
selectableRewardItems: make([]CollectionRewardItem, 0),
lastModified: time.Now(),
lastModified: time.Now(),
}
}
@ -28,17 +28,17 @@ func NewCollectionFromData(source *Collection) *Collection {
collection := &Collection{
id: source.id,
name: source.name,
category: source.category,
level: source.level,
rewardCoin: source.rewardCoin,
rewardXP: source.rewardXP,
completed: source.completed,
saveNeeded: source.saveNeeded,
collectionItems: make([]CollectionItem, len(source.collectionItems)),
rewardItems: make([]CollectionRewardItem, len(source.rewardItems)),
name: source.name,
category: source.category,
level: source.level,
rewardCoin: source.rewardCoin,
rewardXP: source.rewardXP,
completed: source.completed,
saveNeeded: source.saveNeeded,
collectionItems: make([]CollectionItem, len(source.collectionItems)),
rewardItems: make([]CollectionRewardItem, len(source.rewardItems)),
selectableRewardItems: make([]CollectionRewardItem, len(source.selectableRewardItems)),
lastModified: time.Now(),
lastModified: time.Now(),
}
// Deep copy collection items

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"sort"
"strings"
)
// NewMasterCollectionList creates a new master collection list

View File

@ -20,19 +20,19 @@ type CollectionRewardItem struct {
// Collection represents a collection that players can complete
type Collection struct {
mu sync.RWMutex
id int32
name string
category string
level int8
rewardCoin int64
rewardXP int64
completed bool
saveNeeded bool
collectionItems []CollectionItem
rewardItems []CollectionRewardItem
selectableRewardItems []CollectionRewardItem
lastModified time.Time
mu sync.RWMutex
id int32
name string
category string
level int8
rewardCoin int64
rewardXP int64
completed bool
saveNeeded bool
collectionItems []CollectionItem
rewardItems []CollectionRewardItem
selectableRewardItems []CollectionRewardItem
lastModified time.Time
}
// CollectionData represents collection data for database operations
@ -60,9 +60,9 @@ type PlayerCollectionData struct {
// PlayerCollectionItemData represents player found collection items
type PlayerCollectionItemData struct {
CharacterID int32 `json:"char_id" db:"char_id"`
CollectionID int32 `json:"collection_id" db:"collection_id"`
CollectionItemID int32 `json:"collection_item_id" db:"collection_item_id"`
CharacterID int32 `json:"char_id" db:"char_id"`
CollectionID int32 `json:"collection_id" db:"collection_id"`
CollectionItemID int32 `json:"collection_item_id" db:"collection_item_id"`
}
// MasterCollectionList manages all available collections in the game
@ -100,31 +100,31 @@ type CollectionStatistics struct {
// CollectionInfo provides basic collection information
type CollectionInfo struct {
ID int32 `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Level int8 `json:"level"`
Completed bool `json:"completed"`
ReadyToTurnIn bool `json:"ready_to_turn_in"`
ItemsFound int `json:"items_found"`
ItemsTotal int `json:"items_total"`
RewardCoin int64 `json:"reward_coin"`
RewardXP int64 `json:"reward_xp"`
RewardItems []CollectionRewardItem `json:"reward_items"`
ID int32 `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Level int8 `json:"level"`
Completed bool `json:"completed"`
ReadyToTurnIn bool `json:"ready_to_turn_in"`
ItemsFound int `json:"items_found"`
ItemsTotal int `json:"items_total"`
RewardCoin int64 `json:"reward_coin"`
RewardXP int64 `json:"reward_xp"`
RewardItems []CollectionRewardItem `json:"reward_items"`
SelectableRewards []CollectionRewardItem `json:"selectable_rewards"`
RequiredItems []CollectionItem `json:"required_items"`
RequiredItems []CollectionItem `json:"required_items"`
}
// CollectionProgress represents player progress on a collection
type CollectionProgress struct {
CollectionID int32 `json:"collection_id"`
Name string `json:"name"`
Category string `json:"category"`
Level int8 `json:"level"`
Completed bool `json:"completed"`
ReadyToTurnIn bool `json:"ready_to_turn_in"`
Progress float64 `json:"progress_percentage"`
ItemsFound []CollectionItem `json:"items_found"`
ItemsNeeded []CollectionItem `json:"items_needed"`
LastUpdated time.Time `json:"last_updated"`
CollectionID int32 `json:"collection_id"`
Name string `json:"name"`
Category string `json:"category"`
Level int8 `json:"level"`
Completed bool `json:"completed"`
ReadyToTurnIn bool `json:"ready_to_turn_in"`
Progress float64 `json:"progress_percentage"`
ItemsFound []CollectionItem `json:"items_found"`
ItemsNeeded []CollectionItem `json:"items_needed"`
LastUpdated time.Time `json:"last_updated"`
}

View File

@ -32,10 +32,10 @@ func (vs *VisualState) GetName() string {
// Emote represents an emote with visual state and messages
type Emote struct {
name string
visualState int32
message string
targetedMessage string
name string
visualState int32
message string
targetedMessage string
}
// NewEmote creates a new emote

View File

@ -25,13 +25,13 @@ var NewSpellEffectManager = spells.NewSpellEffectManager
// Re-export constants
const (
ControlEffectStun = spells.ControlEffectStun
ControlEffectRoot = spells.ControlEffectRoot
ControlEffectMez = spells.ControlEffectMez
ControlEffectDaze = spells.ControlEffectDaze
ControlEffectFear = spells.ControlEffectFear
ControlEffectSlow = spells.ControlEffectSlow
ControlEffectStun = spells.ControlEffectStun
ControlEffectRoot = spells.ControlEffectRoot
ControlEffectMez = spells.ControlEffectMez
ControlEffectDaze = spells.ControlEffectDaze
ControlEffectFear = spells.ControlEffectFear
ControlEffectSlow = spells.ControlEffectSlow
ControlEffectSnare = spells.ControlEffectSnare
ControlEffectCharm = spells.ControlEffectCharm
ControlMaxEffects = spells.ControlMaxEffects
ControlMaxEffects = spells.ControlMaxEffects
)

View File

@ -7,11 +7,11 @@ const (
MinFactionValue = -50000
// Special faction ID ranges
SpecialFactionIDMax = 10 // Faction IDs <= 10 are special (not real factions)
SpecialFactionIDMax = 10 // Faction IDs <= 10 are special (not real factions)
// Faction consideration (con) ranges
MinCon = -4 // Hostile
MaxCon = 4 // Ally
MinCon = -4 // Hostile
MaxCon = 4 // Ally
// Con value thresholds
ConNeutralMin = -9999
@ -34,13 +34,13 @@ const AttackThreshold = -4
// Default faction consideration values
const (
ConKOS = -4 // Kill on sight
ConThreat = -3 // Threatening
ConDubious = -2 // Dubiously
ConAppre = -1 // Apprehensive
ConIndiff = 0 // Indifferent
ConAmiable = 1 // Amiable
ConKindly = 2 // Kindly
ConWarmly = 3 // Warmly
ConAlly = 4 // Ally
ConKOS = -4 // Kill on sight
ConThreat = -3 // Threatening
ConDubious = -2 // Dubiously
ConAppre = -1 // Apprehensive
ConIndiff = 0 // Indifferent
ConAmiable = 1 // Amiable
ConKindly = 2 // Kindly
ConWarmly = 3 // Warmly
ConAlly = 4 // Ally
)

View File

@ -13,12 +13,12 @@ type Manager struct {
mutex sync.RWMutex
// Statistics
totalFactionChanges int64
factionIncreases int64
factionDecreases int64
factionLookups int64
playersWithFactions int64
changesByFaction map[int32]int64 // Faction ID -> total changes
totalFactionChanges int64
factionIncreases int64
factionDecreases int64
factionLookups int64
playersWithFactions int64
changesByFaction map[int32]int64 // Faction ID -> total changes
}
// NewManager creates a new faction manager

View File

@ -7,11 +7,11 @@ import (
// MasterFactionList manages all factions in the game
type MasterFactionList struct {
globalFactionList map[int32]*Faction // Factions by ID
factionNameList map[string]*Faction // Factions by name
hostileFactions map[int32][]int32 // Hostile faction relationships
friendlyFactions map[int32][]int32 // Friendly faction relationships
mutex sync.RWMutex // Thread safety
globalFactionList map[int32]*Faction // Factions by ID
factionNameList map[string]*Faction // Factions by name
hostileFactions map[int32][]int32 // Hostile faction relationships
friendlyFactions map[int32][]int32 // Friendly faction relationships
mutex sync.RWMutex // Thread safety
}
// NewMasterFactionList creates a new master faction list

View File

@ -6,12 +6,12 @@ import (
// PlayerFaction manages faction standing for a single player
type PlayerFaction struct {
factionValues map[int32]int32 // Faction ID -> current value
factionPercent map[int32]int8 // Faction ID -> percentage within con level
factionUpdateNeeded []int32 // Factions that need client updates
factionValues map[int32]int32 // Faction ID -> current value
factionPercent map[int32]int8 // Faction ID -> percentage within con level
factionUpdateNeeded []int32 // Factions that need client updates
masterFactionList *MasterFactionList
updateMutex sync.Mutex // Thread safety for updates
mutex sync.RWMutex // Thread safety for faction data
updateMutex sync.Mutex // Thread safety for updates
mutex sync.RWMutex // Thread safety for faction data
}
// NewPlayerFaction creates a new player faction system

View File

@ -2,25 +2,25 @@ package factions
// Faction represents a single faction with its properties
type Faction struct {
ID int32 // Faction ID
Name string // Faction name
Type string // Faction type/category
Description string // Faction description
NegativeChange int16 // Amount faction decreases by default
PositiveChange int16 // Amount faction increases by default
DefaultValue int32 // Default faction value for new characters
ID int32 // Faction ID
Name string // Faction name
Type string // Faction type/category
Description string // Faction description
NegativeChange int16 // Amount faction decreases by default
PositiveChange int16 // Amount faction increases by default
DefaultValue int32 // Default faction value for new characters
}
// NewFaction creates a new faction with the given parameters
func NewFaction(id int32, name, factionType, description string) *Faction {
return &Faction{
ID: id,
Name: name,
Type: factionType,
Description: description,
NegativeChange: 0,
PositiveChange: 0,
DefaultValue: 0,
ID: id,
Name: name,
Type: factionType,
Description: description,
NegativeChange: 0,
PositiveChange: 0,
DefaultValue: 0,
}
}
@ -77,13 +77,13 @@ func (f *Faction) SetDefaultValue(value int32) {
// Clone creates a copy of the faction
func (f *Faction) Clone() *Faction {
return &Faction{
ID: f.ID,
Name: f.Name,
Type: f.Type,
Description: f.Description,
NegativeChange: f.NegativeChange,
PositiveChange: f.PositiveChange,
DefaultValue: f.DefaultValue,
ID: f.ID,
Name: f.Name,
Type: f.Type,
Description: f.Description,
NegativeChange: f.NegativeChange,
PositiveChange: f.PositiveChange,
DefaultValue: f.DefaultValue,
}
}

View File

@ -55,18 +55,18 @@ const (
// Default spawn configuration
const (
DefaultDifficulty = 0
DefaultSpawnType = 2
DefaultState = 129
DefaultDifficulty = 0
DefaultSpawnType = 2
DefaultState = 129
DefaultAttemptsPerHarvest = 1
DefaultNumberHarvests = 1
DefaultRandomizeHeading = true
DefaultNumberHarvests = 1
DefaultRandomizeHeading = true
)
// Harvest message channels (placeholder values)
const (
ChannelHarvesting = 15
ChannelColorRed = 13
ChannelHarvesting = 15
ChannelColorRed = 13
)
// Statistical tracking

View File

@ -142,9 +142,9 @@ type GroundSpawnAware interface {
// PlayerGroundSpawnAdapter provides ground spawn functionality for players
type PlayerGroundSpawnAdapter struct {
player *Player
manager *Manager
logger Logger
player *Player
manager *Manager
logger Logger
}
// NewPlayerGroundSpawnAdapter creates a new player ground spawn adapter

View File

@ -2,21 +2,20 @@ package ground_spawn
import (
"fmt"
"sync"
"time"
)
// NewManager creates a new ground spawn manager
func NewManager(database Database, logger Logger) *Manager {
return &Manager{
groundSpawns: make(map[int32]*GroundSpawn),
spawnsByZone: make(map[int32][]*GroundSpawn),
entriesByID: make(map[int32][]*GroundSpawnEntry),
itemsByID: make(map[int32][]*GroundSpawnEntryItem),
respawnQueue: make(map[int32]time.Time),
database: database,
logger: logger,
harvestsBySkill: make(map[string]int64),
groundSpawns: make(map[int32]*GroundSpawn),
spawnsByZone: make(map[int32][]*GroundSpawn),
entriesByID: make(map[int32][]*GroundSpawnEntry),
itemsByID: make(map[int32][]*GroundSpawnEntryItem),
respawnQueue: make(map[int32]time.Time),
database: database,
logger: logger,
harvestsBySkill: make(map[string]int64),
}
}
@ -229,14 +228,14 @@ func (m *Manager) buildHarvestContext(gs *GroundSpawn, player *Player) (*Harvest
}
return &HarvestContext{
Player: player,
GroundSpawn: gs,
PlayerSkill: playerSkill,
TotalSkill: totalSkill,
GroundSpawnEntries: entries,
GroundSpawnItems: items,
IsCollection: gs.GetCollectionSkill() == SkillCollecting,
MaxSkillRequired: maxSkillRequired,
Player: player,
GroundSpawn: gs,
PlayerSkill: playerSkill,
TotalSkill: totalSkill,
GroundSpawnEntries: entries,
GroundSpawnItems: items,
IsCollection: gs.GetCollectionSkill() == SkillCollecting,
MaxSkillRequired: maxSkillRequired,
}, nil
}
@ -302,13 +301,13 @@ func (m *Manager) GetStatistics() *HarvestStatistics {
}
return &HarvestStatistics{
TotalHarvests: m.totalHarvests,
SuccessfulHarvests: m.successfulHarvests,
RareItemsHarvested: m.rareItemsHarvested,
SkillUpsGenerated: m.skillUpsGenerated,
HarvestsBySkill: harvestsBySkill,
ActiveGroundSpawns: len(m.groundSpawns),
GroundSpawnsByZone: spawnsByZone,
TotalHarvests: m.totalHarvests,
SuccessfulHarvests: m.successfulHarvests,
RareItemsHarvested: m.rareItemsHarvested,
SkillUpsGenerated: m.skillUpsGenerated,
HarvestsBySkill: harvestsBySkill,
ActiveGroundSpawns: len(m.groundSpawns),
GroundSpawnsByZone: spawnsByZone,
}
}

View File

@ -4,22 +4,21 @@ import (
"sync"
"time"
"eq2emu/internal/common"
"eq2emu/internal/spawn"
)
// GroundSpawn represents a harvestable resource node in the game world
type GroundSpawn struct {
*spawn.Spawn // Embed spawn for base functionality
*spawn.Spawn // Embed spawn for base functionality
numberHarvests int8 // Number of harvests remaining
numAttemptsPerHarvest int8 // Attempts per harvest session
groundspawnID int32 // Database ID for this groundspawn entry
collectionSkill string // Required skill for harvesting
randomizeHeading bool // Whether to randomize heading on spawn
numberHarvests int8 // Number of harvests remaining
numAttemptsPerHarvest int8 // Attempts per harvest session
groundspawnID int32 // Database ID for this groundspawn entry
collectionSkill string // Required skill for harvesting
randomizeHeading bool // Whether to randomize heading on spawn
harvestMutex sync.Mutex // Thread safety for harvest operations
harvestUseMutex sync.Mutex // Thread safety for use operations
harvestMutex sync.Mutex // Thread safety for harvest operations
harvestUseMutex sync.Mutex // Thread safety for use operations
}
// GroundSpawnEntry represents harvest table data from database
@ -38,40 +37,40 @@ type GroundSpawnEntry struct {
// GroundSpawnEntryItem represents items that can be harvested
type GroundSpawnEntryItem struct {
ItemID int32 // Item database ID
IsRare int8 // 0=normal, 1=rare, 2=imbue
GridID int32 // Grid restriction (0=any)
Quantity int16 // Item quantity (usually 1)
ItemID int32 // Item database ID
IsRare int8 // 0=normal, 1=rare, 2=imbue
GridID int32 // Grid restriction (0=any)
Quantity int16 // Item quantity (usually 1)
}
// HarvestResult represents the outcome of a harvest attempt
type HarvestResult struct {
Success bool // Whether harvest succeeded
HarvestType int8 // Type of harvest achieved
ItemsAwarded []*HarvestedItem // Items given to player
MessageText string // Message to display to player
SkillGained bool // Whether skill was gained
Error error // Any error that occurred
Success bool // Whether harvest succeeded
HarvestType int8 // Type of harvest achieved
ItemsAwarded []*HarvestedItem // Items given to player
MessageText string // Message to display to player
SkillGained bool // Whether skill was gained
Error error // Any error that occurred
}
// HarvestedItem represents an item awarded from harvesting
type HarvestedItem struct {
ItemID int32 // Database item ID
Quantity int16 // Number of items
IsRare bool // Whether this is a rare item
Name string // Item name for messages
ItemID int32 // Database item ID
Quantity int16 // Number of items
IsRare bool // Whether this is a rare item
Name string // Item name for messages
}
// HarvestContext contains all data needed for a harvest operation
type HarvestContext struct {
Player *Player // Player attempting harvest
GroundSpawn *GroundSpawn // The ground spawn being harvested
PlayerSkill *Skill // Player's harvesting skill
TotalSkill int16 // Total skill including bonuses
GroundSpawnEntries []*GroundSpawnEntry // Available harvest tables
GroundSpawnItems []*GroundSpawnEntryItem // Available harvest items
IsCollection bool // Whether this is collection harvesting
MaxSkillRequired int16 // Maximum skill required for any table
Player *Player // Player attempting harvest
GroundSpawn *GroundSpawn // The ground spawn being harvested
PlayerSkill *Skill // Player's harvesting skill
TotalSkill int16 // Total skill including bonuses
GroundSpawnEntries []*GroundSpawnEntry // Available harvest tables
GroundSpawnItems []*GroundSpawnEntryItem // Available harvest items
IsCollection bool // Whether this is collection harvesting
MaxSkillRequired int16 // Maximum skill required for any table
}
// SpawnLocation represents a spawn position with grid information
@ -93,45 +92,45 @@ type HarvestModifiers struct {
// GroundSpawnConfig contains configuration for ground spawn creation
type GroundSpawnConfig struct {
GroundSpawnID int32 // Database entry ID
CollectionSkill string // Required harvesting skill
NumberHarvests int8 // Harvests before depletion
AttemptsPerHarvest int8 // Attempts per harvest session
RandomizeHeading bool // Randomize spawn heading
RespawnTimer time.Duration // Time before respawn
Location SpawnLocation // Spawn position
Name string // Display name
Description string // Spawn description
GroundSpawnID int32 // Database entry ID
CollectionSkill string // Required harvesting skill
NumberHarvests int8 // Harvests before depletion
AttemptsPerHarvest int8 // Attempts per harvest session
RandomizeHeading bool // Randomize spawn heading
RespawnTimer time.Duration // Time before respawn
Location SpawnLocation // Spawn position
Name string // Display name
Description string // Spawn description
}
// Manager manages all ground spawn operations
type Manager struct {
groundSpawns map[int32]*GroundSpawn // Active ground spawns by ID
spawnsByZone map[int32][]*GroundSpawn // Ground spawns by zone ID
entriesByID map[int32][]*GroundSpawnEntry // Harvest entries by groundspawn ID
itemsByID map[int32][]*GroundSpawnEntryItem // Harvest items by groundspawn ID
respawnQueue map[int32]time.Time // Respawn timestamps
groundSpawns map[int32]*GroundSpawn // Active ground spawns by ID
spawnsByZone map[int32][]*GroundSpawn // Ground spawns by zone ID
entriesByID map[int32][]*GroundSpawnEntry // Harvest entries by groundspawn ID
itemsByID map[int32][]*GroundSpawnEntryItem // Harvest items by groundspawn ID
respawnQueue map[int32]time.Time // Respawn timestamps
database Database // Database interface
logger Logger // Logging interface
database Database // Database interface
logger Logger // Logging interface
mutex sync.RWMutex // Thread safety
mutex sync.RWMutex // Thread safety
// Statistics
totalHarvests int64 // Total harvest attempts
successfulHarvests int64 // Successful harvests
rareItemsHarvested int64 // Rare items harvested
skillUpsGenerated int64 // Skill increases given
harvestsBySkill map[string]int64 // Harvests by skill type
totalHarvests int64 // Total harvest attempts
successfulHarvests int64 // Successful harvests
rareItemsHarvested int64 // Rare items harvested
skillUpsGenerated int64 // Skill increases given
harvestsBySkill map[string]int64 // Harvests by skill type
}
// HarvestStatistics contains harvest system statistics
type HarvestStatistics struct {
TotalHarvests int64 `json:"total_harvests"`
SuccessfulHarvests int64 `json:"successful_harvests"`
RareItemsHarvested int64 `json:"rare_items_harvested"`
SkillUpsGenerated int64 `json:"skill_ups_generated"`
HarvestsBySkill map[string]int64 `json:"harvests_by_skill"`
ActiveGroundSpawns int `json:"active_ground_spawns"`
GroundSpawnsByZone map[int32]int `json:"ground_spawns_by_zone"`
TotalHarvests int64 `json:"total_harvests"`
SuccessfulHarvests int64 `json:"successful_harvests"`
RareItemsHarvested int64 `json:"rare_items_harvested"`
SkillUpsGenerated int64 `json:"skill_ups_generated"`
HarvestsBySkill map[string]int64 `json:"harvests_by_skill"`
ActiveGroundSpawns int `json:"active_ground_spawns"`
GroundSpawnsByZone map[int32]int `json:"ground_spawns_by_zone"`
}

View File

@ -2,106 +2,106 @@ package groups
// Group loot method constants
const (
LOOT_METHOD_LEADER_ONLY = 0
LOOT_METHOD_ROUND_ROBIN = 1
LOOT_METHOD_LEADER_ONLY = 0
LOOT_METHOD_ROUND_ROBIN = 1
LOOT_METHOD_NEED_BEFORE_GREED = 2
LOOT_METHOD_LOTTO = 3
LOOT_METHOD_LOTTO = 3
)
// Group loot rarity constants
const (
LOOT_RARITY_COMMON = 0
LOOT_RARITY_UNCOMMON = 1
LOOT_RARITY_RARE = 2
LOOT_RARITY_LEGENDARY = 3
LOOT_RARITY_FABLED = 4
LOOT_RARITY_COMMON = 0
LOOT_RARITY_UNCOMMON = 1
LOOT_RARITY_RARE = 2
LOOT_RARITY_LEGENDARY = 3
LOOT_RARITY_FABLED = 4
)
// Group auto-split constants
const (
AUTO_SPLIT_DISABLED = 0
AUTO_SPLIT_ENABLED = 1
AUTO_SPLIT_DISABLED = 0
AUTO_SPLIT_ENABLED = 1
)
// Group lock method constants
const (
LOCK_METHOD_OPEN = 0
LOCK_METHOD_INVITE_ONLY = 1
LOCK_METHOD_CLOSED = 2
LOCK_METHOD_OPEN = 0
LOCK_METHOD_INVITE_ONLY = 1
LOCK_METHOD_CLOSED = 2
)
// Group auto-lock constants
const (
AUTO_LOCK_DISABLED = 0
AUTO_LOCK_ENABLED = 1
AUTO_LOCK_DISABLED = 0
AUTO_LOCK_ENABLED = 1
)
// Group auto-loot method constants
const (
AUTO_LOOT_DISABLED = 0
AUTO_LOOT_ENABLED = 1
AUTO_LOOT_DISABLED = 0
AUTO_LOOT_ENABLED = 1
)
// Default yell constants
const (
DEFAULT_YELL_DISABLED = 0
DEFAULT_YELL_ENABLED = 1
DEFAULT_YELL_DISABLED = 0
DEFAULT_YELL_ENABLED = 1
)
// Group size limits
const (
MAX_GROUP_SIZE = 6
MAX_RAID_GROUPS = 4
MAX_RAID_SIZE = MAX_GROUP_SIZE * MAX_RAID_GROUPS
MAX_GROUP_SIZE = 6
MAX_RAID_GROUPS = 4
MAX_RAID_SIZE = MAX_GROUP_SIZE * MAX_RAID_GROUPS
)
// Group member position constants
const (
GROUP_POSITION_LEADER = 0
GROUP_POSITION_MEMBER_1 = 1
GROUP_POSITION_MEMBER_2 = 2
GROUP_POSITION_MEMBER_3 = 3
GROUP_POSITION_MEMBER_4 = 4
GROUP_POSITION_MEMBER_5 = 5
GROUP_POSITION_LEADER = 0
GROUP_POSITION_MEMBER_1 = 1
GROUP_POSITION_MEMBER_2 = 2
GROUP_POSITION_MEMBER_3 = 3
GROUP_POSITION_MEMBER_4 = 4
GROUP_POSITION_MEMBER_5 = 5
)
// Group invite error codes
const (
GROUP_INVITE_SUCCESS = 0
GROUP_INVITE_ALREADY_IN_GROUP = 1
GROUP_INVITE_ALREADY_HAS_INVITE = 2
GROUP_INVITE_GROUP_FULL = 3
GROUP_INVITE_DECLINED = 4
GROUP_INVITE_TARGET_NOT_FOUND = 5
GROUP_INVITE_SELF_INVITE = 6
GROUP_INVITE_PERMISSION_DENIED = 7
GROUP_INVITE_TARGET_BUSY = 8
GROUP_INVITE_SUCCESS = 0
GROUP_INVITE_ALREADY_IN_GROUP = 1
GROUP_INVITE_ALREADY_HAS_INVITE = 2
GROUP_INVITE_GROUP_FULL = 3
GROUP_INVITE_DECLINED = 4
GROUP_INVITE_TARGET_NOT_FOUND = 5
GROUP_INVITE_SELF_INVITE = 6
GROUP_INVITE_PERMISSION_DENIED = 7
GROUP_INVITE_TARGET_BUSY = 8
)
// Group message types
const (
GROUP_MESSAGE_TYPE_SYSTEM = 0
GROUP_MESSAGE_TYPE_COMBAT = 1
GROUP_MESSAGE_TYPE_LOOT = 2
GROUP_MESSAGE_TYPE_QUEST = 3
GROUP_MESSAGE_TYPE_CHAT = 4
GROUP_MESSAGE_TYPE_SYSTEM = 0
GROUP_MESSAGE_TYPE_COMBAT = 1
GROUP_MESSAGE_TYPE_LOOT = 2
GROUP_MESSAGE_TYPE_QUEST = 3
GROUP_MESSAGE_TYPE_CHAT = 4
)
// Channel constants for group communication
const (
CHANNEL_GROUP_SAY = 11
CHANNEL_GROUP_CHAT = 31
CHANNEL_RAID_SAY = 35
CHANNEL_GROUP_SAY = 11
CHANNEL_GROUP_CHAT = 31
CHANNEL_RAID_SAY = 35
)
// Group update flags
const (
GROUP_UPDATE_FLAG_MEMBER_LIST = 1 << 0
GROUP_UPDATE_FLAG_MEMBER_STATS = 1 << 1
GROUP_UPDATE_FLAG_MEMBER_ZONE = 1 << 2
GROUP_UPDATE_FLAG_LEADERSHIP = 1 << 3
GROUP_UPDATE_FLAG_OPTIONS = 1 << 4
GROUP_UPDATE_FLAG_RAID_INFO = 1 << 5
GROUP_UPDATE_FLAG_MEMBER_LIST = 1 << 0
GROUP_UPDATE_FLAG_MEMBER_STATS = 1 << 1
GROUP_UPDATE_FLAG_MEMBER_ZONE = 1 << 2
GROUP_UPDATE_FLAG_LEADERSHIP = 1 << 3
GROUP_UPDATE_FLAG_OPTIONS = 1 << 4
GROUP_UPDATE_FLAG_RAID_INFO = 1 << 5
)
// Raid group constants
@ -121,13 +121,13 @@ const (
// Group timing constants (in milliseconds)
const (
GROUP_UPDATE_INTERVAL = 1000 // 1 second
GROUP_INVITE_TIMEOUT = 30000 // 30 seconds
GROUP_BUFF_UPDATE_INTERVAL = 5000 // 5 seconds
GROUP_UPDATE_INTERVAL = 1000 // 1 second
GROUP_INVITE_TIMEOUT = 30000 // 30 seconds
GROUP_BUFF_UPDATE_INTERVAL = 5000 // 5 seconds
)
// Group validation constants
const (
MIN_GROUP_ID = 1
MAX_GROUP_ID = 2147483647 // Max int32
MIN_GROUP_ID = 1
MAX_GROUP_ID = 2147483647 // Max int32
)

View File

@ -2,8 +2,6 @@ package groups
import (
"fmt"
"sync"
"sync/atomic"
"time"
"eq2emu/internal/entity"
@ -92,13 +90,13 @@ func (g *Group) AddMember(member entity.Entity, isLeader bool) error {
// Create new group member info
gmi := &GroupMemberInfo{
GroupID: g.id,
Name: member.GetName(),
Leader: isLeader,
Member: member,
IsClient: member.IsPlayer(),
JoinTime: time.Now(),
LastUpdate: time.Now(),
GroupID: g.id,
Name: member.GetName(),
Leader: isLeader,
Member: member,
IsClient: member.IsPlayer(),
JoinTime: time.Now(),
LastUpdate: time.Now(),
}
// Update member stats from entity
@ -154,29 +152,29 @@ func (g *Group) AddMemberFromPeer(name string, isLeader, isClient bool, classID
// Create new group member info for peer member
gmi := &GroupMemberInfo{
GroupID: g.id,
Name: name,
Zone: zoneName,
HPCurrent: hpCur,
HPMax: hpMax,
PowerCurrent: powerCur,
PowerMax: powerMax,
LevelCurrent: levelCur,
LevelMax: levelMax,
RaceID: raceID,
ClassID: classID,
Leader: isLeader,
IsClient: isClient,
ZoneID: zoneID,
InstanceID: instanceID,
MentorTargetCharID: mentorTargetCharID,
ClientPeerAddress: peerAddress,
ClientPeerPort: peerPort,
IsRaidLooter: isRaidLooter,
Member: nil, // No local entity reference for peer members
Client: nil, // No local client reference for peer members
JoinTime: time.Now(),
LastUpdate: time.Now(),
GroupID: g.id,
Name: name,
Zone: zoneName,
HPCurrent: hpCur,
HPMax: hpMax,
PowerCurrent: powerCur,
PowerMax: powerMax,
LevelCurrent: levelCur,
LevelMax: levelMax,
RaceID: raceID,
ClassID: classID,
Leader: isLeader,
IsClient: isClient,
ZoneID: zoneID,
InstanceID: instanceID,
MentorTargetCharID: mentorTargetCharID,
ClientPeerAddress: peerAddress,
ClientPeerPort: peerPort,
IsRaidLooter: isRaidLooter,
Member: nil, // No local entity reference for peer members
Client: nil, // No local client reference for peer members
JoinTime: time.Now(),
LastUpdate: time.Now(),
}
// Add to members list

View File

@ -2,6 +2,7 @@ package groups
import (
"eq2emu/internal/entity"
"time"
)
// GroupAware interface for entities that can be part of groups

View File

@ -2,8 +2,6 @@ package groups
import (
"fmt"
"sync"
"sync/atomic"
"time"
"eq2emu/internal/entity"

View File

@ -22,21 +22,21 @@ type ServiceConfig struct {
ManagerConfig GroupManagerConfig `json:"manager_config"`
// Service-specific settings
AutoCreateGroups bool `json:"auto_create_groups"`
AllowCrossZoneGroups bool `json:"allow_cross_zone_groups"`
AllowBotMembers bool `json:"allow_bot_members"`
AllowNPCMembers bool `json:"allow_npc_members"`
MaxInviteDistance float32 `json:"max_invite_distance"`
GroupLevelRange int8 `json:"group_level_range"`
EnableGroupPvP bool `json:"enable_group_pvp"`
EnableGroupBuffs bool `json:"enable_group_buffs"`
LogLevel string `json:"log_level"`
AutoCreateGroups bool `json:"auto_create_groups"`
AllowCrossZoneGroups bool `json:"allow_cross_zone_groups"`
AllowBotMembers bool `json:"allow_bot_members"`
AllowNPCMembers bool `json:"allow_npc_members"`
MaxInviteDistance float32 `json:"max_invite_distance"`
GroupLevelRange int8 `json:"group_level_range"`
EnableGroupPvP bool `json:"enable_group_pvp"`
EnableGroupBuffs bool `json:"enable_group_buffs"`
LogLevel string `json:"log_level"`
// Integration settings
DatabaseEnabled bool `json:"database_enabled"`
EventsEnabled bool `json:"events_enabled"`
StatisticsEnabled bool `json:"statistics_enabled"`
ValidationEnabled bool `json:"validation_enabled"`
DatabaseEnabled bool `json:"database_enabled"`
EventsEnabled bool `json:"events_enabled"`
StatisticsEnabled bool `json:"statistics_enabled"`
ValidationEnabled bool `json:"validation_enabled"`
}
// DefaultServiceConfig returns default service configuration

View File

@ -9,208 +9,208 @@ import (
// GroupOptions holds group configuration settings
type GroupOptions struct {
LootMethod int8 `json:"loot_method"`
LootItemsRarity int8 `json:"loot_items_rarity"`
AutoSplit int8 `json:"auto_split"`
DefaultYell int8 `json:"default_yell"`
GroupLockMethod int8 `json:"group_lock_method"`
GroupAutolock int8 `json:"group_autolock"`
SoloAutolock int8 `json:"solo_autolock"`
AutoLootMethod int8 `json:"auto_loot_method"`
LastLootedIndex int8 `json:"last_looted_index"`
LootMethod int8 `json:"loot_method"`
LootItemsRarity int8 `json:"loot_items_rarity"`
AutoSplit int8 `json:"auto_split"`
DefaultYell int8 `json:"default_yell"`
GroupLockMethod int8 `json:"group_lock_method"`
GroupAutolock int8 `json:"group_autolock"`
SoloAutolock int8 `json:"solo_autolock"`
AutoLootMethod int8 `json:"auto_loot_method"`
LastLootedIndex int8 `json:"last_looted_index"`
}
// GroupMemberInfo contains all information about a group member
type GroupMemberInfo struct {
// Group and member identification
GroupID int32 `json:"group_id"`
Name string `json:"name"`
Zone string `json:"zone"`
GroupID int32 `json:"group_id"`
Name string `json:"name"`
Zone string `json:"zone"`
// Health and power stats
HPCurrent int32 `json:"hp_current"`
HPMax int32 `json:"hp_max"`
PowerCurrent int32 `json:"power_current"`
PowerMax int32 `json:"power_max"`
HPCurrent int32 `json:"hp_current"`
HPMax int32 `json:"hp_max"`
PowerCurrent int32 `json:"power_current"`
PowerMax int32 `json:"power_max"`
// Level and character info
LevelCurrent int16 `json:"level_current"`
LevelMax int16 `json:"level_max"`
RaceID int8 `json:"race_id"`
ClassID int8 `json:"class_id"`
LevelCurrent int16 `json:"level_current"`
LevelMax int16 `json:"level_max"`
RaceID int8 `json:"race_id"`
ClassID int8 `json:"class_id"`
// Group status
Leader bool `json:"leader"`
IsClient bool `json:"is_client"`
IsRaidLooter bool `json:"is_raid_looter"`
Leader bool `json:"leader"`
IsClient bool `json:"is_client"`
IsRaidLooter bool `json:"is_raid_looter"`
// Zone and instance info
ZoneID int32 `json:"zone_id"`
InstanceID int32 `json:"instance_id"`
ZoneID int32 `json:"zone_id"`
InstanceID int32 `json:"instance_id"`
// Mentoring
MentorTargetCharID int32 `json:"mentor_target_char_id"`
MentorTargetCharID int32 `json:"mentor_target_char_id"`
// Network info for cross-server groups
ClientPeerAddress string `json:"client_peer_address"`
ClientPeerPort int16 `json:"client_peer_port"`
ClientPeerAddress string `json:"client_peer_address"`
ClientPeerPort int16 `json:"client_peer_port"`
// Entity reference (local members only)
Member entity.Entity `json:"-"`
Member entity.Entity `json:"-"`
// Client reference (players only) - interface to avoid circular deps
Client interface{} `json:"-"`
Client interface{} `json:"-"`
// Timestamps
JoinTime time.Time `json:"join_time"`
LastUpdate time.Time `json:"last_update"`
JoinTime time.Time `json:"join_time"`
LastUpdate time.Time `json:"last_update"`
}
// Group represents a player group
type Group struct {
// Group identification
id int32
id int32
// Group options and configuration
options GroupOptions
optionsMutex sync.RWMutex
options GroupOptions
optionsMutex sync.RWMutex
// Group members
members []*GroupMemberInfo
membersMutex sync.RWMutex
members []*GroupMemberInfo
membersMutex sync.RWMutex
// Raid functionality
raidGroups []int32
raidGroupsMutex sync.RWMutex
raidGroups []int32
raidGroupsMutex sync.RWMutex
// Group statistics
createdTime time.Time
lastActivity time.Time
createdTime time.Time
lastActivity time.Time
// Group status
disbanded bool
disbandMutex sync.RWMutex
disbanded bool
disbandMutex sync.RWMutex
// Communication channels
messageQueue chan *GroupMessage
updateQueue chan *GroupUpdate
messageQueue chan *GroupMessage
updateQueue chan *GroupUpdate
// Background processing
stopChan chan struct{}
wg sync.WaitGroup
stopChan chan struct{}
wg sync.WaitGroup
}
// GroupMessage represents a message sent to the group
type GroupMessage struct {
Type int8 `json:"type"`
Channel int16 `json:"channel"`
Message string `json:"message"`
FromName string `json:"from_name"`
Language int32 `json:"language"`
Timestamp time.Time `json:"timestamp"`
ExcludeClient interface{} `json:"-"`
Type int8 `json:"type"`
Channel int16 `json:"channel"`
Message string `json:"message"`
FromName string `json:"from_name"`
Language int32 `json:"language"`
Timestamp time.Time `json:"timestamp"`
ExcludeClient interface{} `json:"-"`
}
// GroupUpdate represents a group update event
type GroupUpdate struct {
Type int8 `json:"type"`
GroupID int32 `json:"group_id"`
MemberInfo *GroupMemberInfo `json:"member_info,omitempty"`
Options *GroupOptions `json:"options,omitempty"`
RaidGroups []int32 `json:"raid_groups,omitempty"`
ForceRaidUpdate bool `json:"force_raid_update"`
ExcludeClient interface{} `json:"-"`
Timestamp time.Time `json:"timestamp"`
Type int8 `json:"type"`
GroupID int32 `json:"group_id"`
MemberInfo *GroupMemberInfo `json:"member_info,omitempty"`
Options *GroupOptions `json:"options,omitempty"`
RaidGroups []int32 `json:"raid_groups,omitempty"`
ForceRaidUpdate bool `json:"force_raid_update"`
ExcludeClient interface{} `json:"-"`
Timestamp time.Time `json:"timestamp"`
}
// GroupInvite represents a pending group invitation
type GroupInvite struct {
InviterName string `json:"inviter_name"`
InviteeName string `json:"invitee_name"`
GroupID int32 `json:"group_id"`
IsRaidInvite bool `json:"is_raid_invite"`
CreatedTime time.Time `json:"created_time"`
ExpiresTime time.Time `json:"expires_time"`
InviterName string `json:"inviter_name"`
InviteeName string `json:"invitee_name"`
GroupID int32 `json:"group_id"`
IsRaidInvite bool `json:"is_raid_invite"`
CreatedTime time.Time `json:"created_time"`
ExpiresTime time.Time `json:"expires_time"`
}
// GroupManager manages all player groups
type GroupManager struct {
// Group storage
groups map[int32]*Group
groupsMutex sync.RWMutex
groups map[int32]*Group
groupsMutex sync.RWMutex
// Group ID generation
nextGroupID int32
nextGroupIDMutex sync.Mutex
nextGroupID int32
nextGroupIDMutex sync.Mutex
// Pending invitations
pendingInvites map[string]*GroupInvite
raidPendingInvites map[string]*GroupInvite
invitesMutex sync.RWMutex
pendingInvites map[string]*GroupInvite
raidPendingInvites map[string]*GroupInvite
invitesMutex sync.RWMutex
// Event handlers
eventHandlers []GroupEventHandler
eventHandlersMutex sync.RWMutex
eventHandlers []GroupEventHandler
eventHandlersMutex sync.RWMutex
// Configuration
config GroupManagerConfig
config GroupManagerConfig
// Statistics
stats GroupManagerStats
statsMutex sync.RWMutex
stats GroupManagerStats
statsMutex sync.RWMutex
// Background processing
stopChan chan struct{}
wg sync.WaitGroup
stopChan chan struct{}
wg sync.WaitGroup
// Integration interfaces
database GroupDatabase
packetHandler GroupPacketHandler
validator GroupValidator
notifier GroupNotifier
database GroupDatabase
packetHandler GroupPacketHandler
validator GroupValidator
notifier GroupNotifier
}
// GroupManagerConfig holds configuration for the group manager
type GroupManagerConfig struct {
MaxGroups int32 `json:"max_groups"`
MaxRaidGroups int32 `json:"max_raid_groups"`
InviteTimeout time.Duration `json:"invite_timeout"`
UpdateInterval time.Duration `json:"update_interval"`
BuffUpdateInterval time.Duration `json:"buff_update_interval"`
EnableCrossServer bool `json:"enable_cross_server"`
EnableRaids bool `json:"enable_raids"`
EnableQuestSharing bool `json:"enable_quest_sharing"`
EnableAutoInvite bool `json:"enable_auto_invite"`
EnableStatistics bool `json:"enable_statistics"`
MaxGroups int32 `json:"max_groups"`
MaxRaidGroups int32 `json:"max_raid_groups"`
InviteTimeout time.Duration `json:"invite_timeout"`
UpdateInterval time.Duration `json:"update_interval"`
BuffUpdateInterval time.Duration `json:"buff_update_interval"`
EnableCrossServer bool `json:"enable_cross_server"`
EnableRaids bool `json:"enable_raids"`
EnableQuestSharing bool `json:"enable_quest_sharing"`
EnableAutoInvite bool `json:"enable_auto_invite"`
EnableStatistics bool `json:"enable_statistics"`
}
// GroupManagerStats holds statistics about group management
type GroupManagerStats struct {
TotalGroups int64 `json:"total_groups"`
ActiveGroups int64 `json:"active_groups"`
TotalRaids int64 `json:"total_raids"`
ActiveRaids int64 `json:"active_raids"`
TotalInvites int64 `json:"total_invites"`
AcceptedInvites int64 `json:"accepted_invites"`
DeclinedInvites int64 `json:"declined_invites"`
ExpiredInvites int64 `json:"expired_invites"`
AverageGroupSize float64 `json:"average_group_size"`
AverageGroupDuration time.Duration `json:"average_group_duration"`
LastStatsUpdate time.Time `json:"last_stats_update"`
TotalGroups int64 `json:"total_groups"`
ActiveGroups int64 `json:"active_groups"`
TotalRaids int64 `json:"total_raids"`
ActiveRaids int64 `json:"active_raids"`
TotalInvites int64 `json:"total_invites"`
AcceptedInvites int64 `json:"accepted_invites"`
DeclinedInvites int64 `json:"declined_invites"`
ExpiredInvites int64 `json:"expired_invites"`
AverageGroupSize float64 `json:"average_group_size"`
AverageGroupDuration time.Duration `json:"average_group_duration"`
LastStatsUpdate time.Time `json:"last_stats_update"`
}
// Default group options
func DefaultGroupOptions() GroupOptions {
return GroupOptions{
LootMethod: LOOT_METHOD_ROUND_ROBIN,
LootItemsRarity: LOOT_RARITY_COMMON,
AutoSplit: AUTO_SPLIT_DISABLED,
DefaultYell: DEFAULT_YELL_DISABLED,
GroupLockMethod: LOCK_METHOD_OPEN,
GroupAutolock: AUTO_LOCK_DISABLED,
SoloAutolock: AUTO_LOCK_DISABLED,
AutoLootMethod: AUTO_LOOT_DISABLED,
LastLootedIndex: 0,
LootMethod: LOOT_METHOD_ROUND_ROBIN,
LootItemsRarity: LOOT_RARITY_COMMON,
AutoSplit: AUTO_SPLIT_DISABLED,
DefaultYell: DEFAULT_YELL_DISABLED,
GroupLockMethod: LOCK_METHOD_OPEN,
GroupAutolock: AUTO_LOCK_DISABLED,
SoloAutolock: AUTO_LOCK_DISABLED,
AutoLootMethod: AUTO_LOOT_DISABLED,
LastLootedIndex: 0,
}
}
@ -256,7 +256,7 @@ func (go_opts *GroupOptions) Copy() GroupOptions {
// IsValid checks if group options are valid
func (go_opts *GroupOptions) IsValid() bool {
return go_opts.LootMethod >= LOOT_METHOD_LEADER_ONLY && go_opts.LootMethod <= LOOT_METHOD_LOTTO &&
go_opts.LootItemsRarity >= LOOT_RARITY_COMMON && go_opts.LootItemsRarity <= LOOT_RARITY_FABLED
go_opts.LootItemsRarity >= LOOT_RARITY_COMMON && go_opts.LootItemsRarity <= LOOT_RARITY_FABLED
}
// NewGroupMessage creates a new group message

View File

@ -2,55 +2,55 @@ package guilds
// Guild rank constants
const (
RankLeader = 0
RankSeniorOfficer = 1
RankOfficer = 2
RankSeniorMember = 3
RankMember = 4
RankJuniorMember = 5
RankInitiate = 6
RankRecruit = 7
RankLeader = 0
RankSeniorOfficer = 1
RankOfficer = 2
RankSeniorMember = 3
RankMember = 4
RankJuniorMember = 5
RankInitiate = 6
RankRecruit = 7
)
// Guild permission constants
const (
PermissionInvite = 0
PermissionRemoveMember = 1
PermissionPromoteMember = 2
PermissionDemoteMember = 3
PermissionChangeMOTD = 6
PermissionChangePermissions = 7
PermissionChangeRankNames = 8
PermissionSeeOfficerNotes = 9
PermissionEditOfficerNotes = 10
PermissionSeeOfficerChat = 11
PermissionSpeakInOfficerChat = 12
PermissionSeeGuildChat = 13
PermissionSpeakInGuildChat = 14
PermissionEditPersonalNotes = 15
PermissionEditPersonalNotesOthers = 16
PermissionEditEventFilters = 17
PermissionEditEvents = 18
PermissionPurchaseStatusItems = 19
PermissionDisplayGuildName = 20
PermissionSendEmailToGuild = 21
PermissionBank1SeeContents = 22
PermissionBank2SeeContents = 23
PermissionBank3SeeContents = 24
PermissionBank4SeeContents = 25
PermissionBank1Deposit = 26
PermissionBank2Deposit = 27
PermissionBank3Deposit = 28
PermissionBank4Deposit = 29
PermissionBank1Withdrawal = 30
PermissionBank2Withdrawal = 31
PermissionBank3Withdrawal = 32
PermissionBank4Withdrawal = 33
PermissionEditRecruitingSettings = 35
PermissionMakeOthersRecruiters = 36
PermissionSeeRecruitingSettings = 37
PermissionAssignPoints = 43
PermissionReceivePoints = 44
PermissionInvite = 0
PermissionRemoveMember = 1
PermissionPromoteMember = 2
PermissionDemoteMember = 3
PermissionChangeMOTD = 6
PermissionChangePermissions = 7
PermissionChangeRankNames = 8
PermissionSeeOfficerNotes = 9
PermissionEditOfficerNotes = 10
PermissionSeeOfficerChat = 11
PermissionSpeakInOfficerChat = 12
PermissionSeeGuildChat = 13
PermissionSpeakInGuildChat = 14
PermissionEditPersonalNotes = 15
PermissionEditPersonalNotesOthers = 16
PermissionEditEventFilters = 17
PermissionEditEvents = 18
PermissionPurchaseStatusItems = 19
PermissionDisplayGuildName = 20
PermissionSendEmailToGuild = 21
PermissionBank1SeeContents = 22
PermissionBank2SeeContents = 23
PermissionBank3SeeContents = 24
PermissionBank4SeeContents = 25
PermissionBank1Deposit = 26
PermissionBank2Deposit = 27
PermissionBank3Deposit = 28
PermissionBank4Deposit = 29
PermissionBank1Withdrawal = 30
PermissionBank2Withdrawal = 31
PermissionBank3Withdrawal = 32
PermissionBank4Withdrawal = 33
PermissionEditRecruitingSettings = 35
PermissionMakeOthersRecruiters = 36
PermissionSeeRecruitingSettings = 37
PermissionAssignPoints = 43
PermissionReceivePoints = 44
)
// Event filter categories
@ -172,22 +172,22 @@ const (
// Recruiting description tags
const (
RecruitingDescTagNone = 0
RecruitingDescTagGood = 1
RecruitingDescTagEvil = 2
RecruitingDescTagChatty = 3
RecruitingDescTagOrganized = 4
RecruitingDescTagRoleplay = 5
RecruitingDescTagEnjoyQuests = 6
RecruitingDescTagEnjoyRaids = 7
RecruitingDescTagOddHours = 8
RecruitingDescTagCrafterOriented = 9
RecruitingDescTagFamilyFriendly = 10
RecruitingDescTagMatureHumor = 11
RecruitingDescTagInmatesRun = 12
RecruitingDescTagVeryFunny = 13
RecruitingDescTagHumorCausesPain = 14
RecruitingDescTagSerious = 15
RecruitingDescTagNone = 0
RecruitingDescTagGood = 1
RecruitingDescTagEvil = 2
RecruitingDescTagChatty = 3
RecruitingDescTagOrganized = 4
RecruitingDescTagRoleplay = 5
RecruitingDescTagEnjoyQuests = 6
RecruitingDescTagEnjoyRaids = 7
RecruitingDescTagOddHours = 8
RecruitingDescTagCrafterOriented = 9
RecruitingDescTagFamilyFriendly = 10
RecruitingDescTagMatureHumor = 11
RecruitingDescTagInmatesRun = 12
RecruitingDescTagVeryFunny = 13
RecruitingDescTagHumorCausesPain = 14
RecruitingDescTagSerious = 15
)
// Member flags
@ -206,14 +206,14 @@ const (
// System limits
const (
MaxGuildLevel = 80
MaxPointHistory = 50
MaxEvents = 500
MaxLockedEvents = 200
MaxGuildNameLength = 64
MaxMOTDLength = 256
MaxMemberNameLength = 64
MaxBankNameLength = 64
MaxGuildLevel = 80
MaxPointHistory = 50
MaxEvents = 500
MaxLockedEvents = 200
MaxGuildNameLength = 64
MaxMOTDLength = 256
MaxMemberNameLength = 64
MaxBankNameLength = 64
MaxRecruitingDescLength = 512
)

View File

@ -573,9 +573,9 @@ func (g *Guild) AddNewGuildMember(characterID int32, invitedBy string, joinDate
}
member := &GuildMember{
CharacterID: characterID,
Rank: rank,
JoinDate: joinDate,
CharacterID: characterID,
Rank: rank,
JoinDate: joinDate,
PointHistory: make([]PointHistory, 0),
}

View File

@ -1,6 +1,9 @@
package guilds
import "context"
import (
"context"
"time"
)
// GuildDatabase defines database operations for guilds
type GuildDatabase interface {
@ -193,15 +196,15 @@ type LogHandler interface {
// PlayerInfo contains basic player information
type PlayerInfo struct {
CharacterID int32 `json:"character_id"`
CharacterName string `json:"character_name"`
AccountID int32 `json:"account_id"`
AdventureLevel int8 `json:"adventure_level"`
AdventureClass int8 `json:"adventure_class"`
TradeskillLevel int8 `json:"tradeskill_level"`
TradeskillClass int8 `json:"tradeskill_class"`
Zone string `json:"zone"`
IsOnline bool `json:"is_online"`
CharacterID int32 `json:"character_id"`
CharacterName string `json:"character_name"`
AccountID int32 `json:"account_id"`
AdventureLevel int8 `json:"adventure_level"`
AdventureClass int8 `json:"adventure_class"`
TradeskillLevel int8 `json:"tradeskill_level"`
TradeskillClass int8 `json:"tradeskill_class"`
Zone string `json:"zone"`
IsOnline bool `json:"is_online"`
LastLogin time.Time `json:"last_login"`
}
@ -334,9 +337,9 @@ type BankManager interface {
// BankItem represents an item in the guild bank
type BankItem struct {
Slot int `json:"slot"`
ItemID int32 `json:"item_id"`
Quantity int32 `json:"quantity"`
DepositorID int32 `json:"depositor_id"`
Slot int `json:"slot"`
ItemID int32 `json:"item_id"`
Quantity int32 `json:"quantity"`
DepositorID int32 `json:"depositor_id"`
DepositDate time.Time `json:"deposit_date"`
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"sort"
"strings"
"sync"
"time"
)
@ -632,24 +631,24 @@ func (gm *GuildManager) loadGuildFromData(ctx context.Context, data GuildData) (
CharacterID: md.CharacterID,
AccountID: md.AccountID,
RecruiterID: md.RecruiterID,
Name: md.Name,
GuildStatus: md.GuildStatus,
Points: md.Points,
AdventureClass: md.AdventureClass,
AdventureLevel: md.AdventureLevel,
TradeskillClass: md.TradeskillClass,
TradeskillLevel: md.TradeskillLevel,
Rank: md.Rank,
MemberFlags: md.MemberFlags,
Zone: md.Zone,
JoinDate: md.JoinDate,
LastLoginDate: md.LastLoginDate,
Note: md.Note,
OfficerNote: md.OfficerNote,
RecruiterDescription: md.RecruiterDescription,
RecruiterPictureData: md.RecruiterPictureData,
Name: md.Name,
GuildStatus: md.GuildStatus,
Points: md.Points,
AdventureClass: md.AdventureClass,
AdventureLevel: md.AdventureLevel,
TradeskillClass: md.TradeskillClass,
TradeskillLevel: md.TradeskillLevel,
Rank: md.Rank,
MemberFlags: md.MemberFlags,
Zone: md.Zone,
JoinDate: md.JoinDate,
LastLoginDate: md.LastLoginDate,
Note: md.Note,
OfficerNote: md.OfficerNote,
RecruiterDescription: md.RecruiterDescription,
RecruiterPictureData: md.RecruiterPictureData,
RecruitingShowAdventureClass: md.RecruitingShowAdventureClass,
PointHistory: make([]PointHistory, 0),
PointHistory: make([]PointHistory, 0),
}
// Load point history

View File

@ -8,11 +8,11 @@ import (
func NewGuildMember(characterID int32, name string, rank int8) *GuildMember {
return &GuildMember{
CharacterID: characterID,
Name: name,
Rank: rank,
JoinDate: time.Now(),
LastLoginDate: time.Now(),
PointHistory: make([]PointHistory, 0),
Name: name,
Rank: rank,
JoinDate: time.Now(),
LastLoginDate: time.Now(),
PointHistory: make([]PointHistory, 0),
RecruitingShowAdventureClass: 1,
}
}
@ -388,12 +388,12 @@ func (gm *GuildMember) GetRecruiterInfo(isOnline bool) RecruiterInfo {
return RecruiterInfo{
CharacterID: gm.CharacterID,
Name: gm.Name,
Description: gm.RecruiterDescription,
PictureData: gm.GetRecruiterPictureData(), // This will make a copy
Name: gm.Name,
Description: gm.RecruiterDescription,
PictureData: gm.GetRecruiterPictureData(), // This will make a copy
ShowAdventureClass: gm.RecruitingShowAdventureClass != 0,
AdventureClass: gm.AdventureClass,
IsOnline: isOnline,
AdventureClass: gm.AdventureClass,
IsOnline: isOnline,
}
}
@ -458,23 +458,23 @@ func (gm *GuildMember) Copy() *GuildMember {
CharacterID: gm.CharacterID,
AccountID: gm.AccountID,
RecruiterID: gm.RecruiterID,
Name: gm.Name,
GuildStatus: gm.GuildStatus,
Points: gm.Points,
AdventureClass: gm.AdventureClass,
AdventureLevel: gm.AdventureLevel,
TradeskillClass: gm.TradeskillClass,
TradeskillLevel: gm.TradeskillLevel,
Rank: gm.Rank,
MemberFlags: gm.MemberFlags,
Zone: gm.Zone,
JoinDate: gm.JoinDate,
LastLoginDate: gm.LastLoginDate,
Note: gm.Note,
OfficerNote: gm.OfficerNote,
RecruiterDescription: gm.RecruiterDescription,
Name: gm.Name,
GuildStatus: gm.GuildStatus,
Points: gm.Points,
AdventureClass: gm.AdventureClass,
AdventureLevel: gm.AdventureLevel,
TradeskillClass: gm.TradeskillClass,
TradeskillLevel: gm.TradeskillLevel,
Rank: gm.Rank,
MemberFlags: gm.MemberFlags,
Zone: gm.Zone,
JoinDate: gm.JoinDate,
LastLoginDate: gm.LastLoginDate,
Note: gm.Note,
OfficerNote: gm.OfficerNote,
RecruiterDescription: gm.RecruiterDescription,
RecruitingShowAdventureClass: gm.RecruitingShowAdventureClass,
PointHistory: make([]PointHistory, len(gm.PointHistory)),
PointHistory: make([]PointHistory, len(gm.PointHistory)),
}
// Deep copy point history

View File

@ -17,27 +17,27 @@ type PointHistory struct {
// GuildMember represents a member of a guild
type GuildMember struct {
mu sync.RWMutex
CharacterID int32 `json:"character_id" db:"character_id"`
AccountID int32 `json:"account_id" db:"account_id"`
RecruiterID int32 `json:"recruiter_id" db:"recruiter_id"`
Name string `json:"name" db:"name"`
GuildStatus int32 `json:"guild_status" db:"guild_status"`
Points float64 `json:"points" db:"points"`
AdventureClass int8 `json:"adventure_class" db:"adventure_class"`
AdventureLevel int8 `json:"adventure_level" db:"adventure_level"`
TradeskillClass int8 `json:"tradeskill_class" db:"tradeskill_class"`
TradeskillLevel int8 `json:"tradeskill_level" db:"tradeskill_level"`
Rank int8 `json:"rank" db:"rank"`
MemberFlags int8 `json:"member_flags" db:"member_flags"`
Zone string `json:"zone" db:"zone"`
JoinDate time.Time `json:"join_date" db:"join_date"`
LastLoginDate time.Time `json:"last_login_date" db:"last_login_date"`
Note string `json:"note" db:"note"`
OfficerNote string `json:"officer_note" db:"officer_note"`
RecruiterDescription string `json:"recruiter_description" db:"recruiter_description"`
RecruiterPictureData []byte `json:"recruiter_picture_data" db:"recruiter_picture_data"`
RecruitingShowAdventureClass int8 `json:"recruiting_show_adventure_class" db:"recruiting_show_adventure_class"`
PointHistory []PointHistory `json:"point_history" db:"-"`
CharacterID int32 `json:"character_id" db:"character_id"`
AccountID int32 `json:"account_id" db:"account_id"`
RecruiterID int32 `json:"recruiter_id" db:"recruiter_id"`
Name string `json:"name" db:"name"`
GuildStatus int32 `json:"guild_status" db:"guild_status"`
Points float64 `json:"points" db:"points"`
AdventureClass int8 `json:"adventure_class" db:"adventure_class"`
AdventureLevel int8 `json:"adventure_level" db:"adventure_level"`
TradeskillClass int8 `json:"tradeskill_class" db:"tradeskill_class"`
TradeskillLevel int8 `json:"tradeskill_level" db:"tradeskill_level"`
Rank int8 `json:"rank" db:"rank"`
MemberFlags int8 `json:"member_flags" db:"member_flags"`
Zone string `json:"zone" db:"zone"`
JoinDate time.Time `json:"join_date" db:"join_date"`
LastLoginDate time.Time `json:"last_login_date" db:"last_login_date"`
Note string `json:"note" db:"note"`
OfficerNote string `json:"officer_note" db:"officer_note"`
RecruiterDescription string `json:"recruiter_description" db:"recruiter_description"`
RecruiterPictureData []byte `json:"recruiter_picture_data" db:"recruiter_picture_data"`
RecruitingShowAdventureClass int8 `json:"recruiting_show_adventure_class" db:"recruiting_show_adventure_class"`
PointHistory []PointHistory `json:"point_history" db:"-"`
}
// GuildEvent represents an event in the guild's history
@ -60,45 +60,45 @@ type GuildBankEvent struct {
// Bank represents a guild bank with its event history
type Bank struct {
Name string `json:"name" db:"name"`
Events []GuildBankEvent `json:"events" db:"-"`
Name string `json:"name" db:"name"`
Events []GuildBankEvent `json:"events" db:"-"`
}
// Guild represents a guild with all its properties and members
type Guild struct {
mu sync.RWMutex
id int32
name string
level int8
formedDate time.Time
motd string
expCurrent int64
expToNextLevel int64
recruitingShortDesc string
recruitingFullDesc string
recruitingMinLevel int8
recruitingPlayStyle int8
members map[int32]*GuildMember
guildEvents []GuildEvent
permissions map[int8]map[int8]int8 // rank -> permission -> value
eventFilters map[int8]map[int8]int8 // event_id -> category -> value
recruitingFlags map[int8]int8
recruitingDescTags map[int8]int8
ranks map[int8]string // rank -> name
banks [4]Bank
mu sync.RWMutex
id int32
name string
level int8
formedDate time.Time
motd string
expCurrent int64
expToNextLevel int64
recruitingShortDesc string
recruitingFullDesc string
recruitingMinLevel int8
recruitingPlayStyle int8
members map[int32]*GuildMember
guildEvents []GuildEvent
permissions map[int8]map[int8]int8 // rank -> permission -> value
eventFilters map[int8]map[int8]int8 // event_id -> category -> value
recruitingFlags map[int8]int8
recruitingDescTags map[int8]int8
ranks map[int8]string // rank -> name
banks [4]Bank
// Save flags
saveNeeded bool
memberSaveNeeded bool
eventsSaveNeeded bool
ranksSaveNeeded bool
eventFiltersSaveNeeded bool
pointsHistorySaveNeeded bool
recruitingSaveNeeded bool
saveNeeded bool
memberSaveNeeded bool
eventsSaveNeeded bool
ranksSaveNeeded bool
eventFiltersSaveNeeded bool
pointsHistorySaveNeeded bool
recruitingSaveNeeded bool
// Tracking
nextEventID int64
lastModified time.Time
nextEventID int64
lastModified time.Time
}
// GuildData represents guild data for database operations
@ -286,38 +286,38 @@ type GuildEventInfo struct {
// GuildSearchCriteria represents guild search parameters
type GuildSearchCriteria struct {
NamePattern string `json:"name_pattern"`
MinLevel int8 `json:"min_level"`
MaxLevel int8 `json:"max_level"`
MinMembers int `json:"min_members"`
MaxMembers int `json:"max_members"`
RecruitingOnly bool `json:"recruiting_only"`
PlayStyle int8 `json:"play_style"`
RequiredFlags []int8 `json:"required_flags"`
RequiredDescTags []int8 `json:"required_desc_tags"`
ExcludedDescTags []int8 `json:"excluded_desc_tags"`
NamePattern string `json:"name_pattern"`
MinLevel int8 `json:"min_level"`
MaxLevel int8 `json:"max_level"`
MinMembers int `json:"min_members"`
MaxMembers int `json:"max_members"`
RecruitingOnly bool `json:"recruiting_only"`
PlayStyle int8 `json:"play_style"`
RequiredFlags []int8 `json:"required_flags"`
RequiredDescTags []int8 `json:"required_desc_tags"`
ExcludedDescTags []int8 `json:"excluded_desc_tags"`
}
// RecruitingInfo provides detailed recruiting information
type RecruitingInfo struct {
GuildID int32 `json:"guild_id"`
GuildName string `json:"guild_name"`
ShortDesc string `json:"short_desc"`
FullDesc string `json:"full_desc"`
MinLevel int8 `json:"min_level"`
PlayStyle int8 `json:"play_style"`
Flags map[int8]int8 `json:"flags"`
DescTags map[int8]int8 `json:"desc_tags"`
Recruiters []RecruiterInfo `json:"recruiters"`
GuildID int32 `json:"guild_id"`
GuildName string `json:"guild_name"`
ShortDesc string `json:"short_desc"`
FullDesc string `json:"full_desc"`
MinLevel int8 `json:"min_level"`
PlayStyle int8 `json:"play_style"`
Flags map[int8]int8 `json:"flags"`
DescTags map[int8]int8 `json:"desc_tags"`
Recruiters []RecruiterInfo `json:"recruiters"`
}
// RecruiterInfo provides recruiter information
type RecruiterInfo struct {
CharacterID int32 `json:"character_id"`
Name string `json:"name"`
Description string `json:"description"`
PictureData []byte `json:"picture_data"`
ShowAdventureClass bool `json:"show_adventure_class"`
AdventureClass int8 `json:"adventure_class"`
IsOnline bool `json:"is_online"`
CharacterID int32 `json:"character_id"`
Name string `json:"name"`
Description string `json:"description"`
PictureData []byte `json:"picture_data"`
ShowAdventureClass bool `json:"show_adventure_class"`
AdventureClass int8 `json:"adventure_class"`
IsOnline bool `json:"is_online"`
}

View File

@ -68,10 +68,10 @@ const (
MaxDatabaseRetries = 3
// Memory management
DefaultHOPoolSize = 100
DefaultStarterCache = 500
DefaultWheelCache = 1000
MaxHOHistoryEntries = 50
DefaultHOPoolSize = 100
DefaultStarterCache = 500
DefaultWheelCache = 1000
MaxHOHistoryEntries = 50
)
// Heroic Opportunity Event Types for logging and statistics

View File

@ -3,7 +3,6 @@ package heroic_ops
import (
"fmt"
"math/rand"
"sync"
"time"
)

View File

@ -154,12 +154,12 @@ type CacheManager interface {
// EncounterInfo contains encounter details
type EncounterInfo struct {
ID int32 `json:"id"`
Name string `json:"name"`
Participants []int32 `json:"participants"`
IsActive bool `json:"is_active"`
ID int32 `json:"id"`
Name string `json:"name"`
Participants []int32 `json:"participants"`
IsActive bool `json:"is_active"`
StartTime time.Time `json:"start_time"`
Level int16 `json:"level"`
Level int16 `json:"level"`
}
// PlayerInfo contains player details needed for HO system

View File

@ -3,7 +3,6 @@ package heroic_ops
import (
"context"
"fmt"
"sync"
"time"
)

View File

@ -3,9 +3,7 @@ package heroic_ops
import (
"context"
"fmt"
"math/rand"
"sort"
"sync"
)
// NewMasterHeroicOPList creates a new master heroic opportunity list

View File

@ -7,54 +7,54 @@ import (
// HeroicOPStarter represents a starter chain for heroic opportunities
type HeroicOPStarter struct {
mu sync.RWMutex
ID int32 `json:"id"` // Unique identifier for this starter
StartClass int8 `json:"start_class"` // Class that can initiate this starter (0 = any)
StarterIcon int16 `json:"starter_icon"` // Icon displayed for the starter
Abilities [6]int16 `json:"abilities"` // Array of ability icons in sequence
Name string `json:"name"` // Display name for this starter
Description string `json:"description"` // Description text
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
mu sync.RWMutex
ID int32 `json:"id"` // Unique identifier for this starter
StartClass int8 `json:"start_class"` // Class that can initiate this starter (0 = any)
StarterIcon int16 `json:"starter_icon"` // Icon displayed for the starter
Abilities [6]int16 `json:"abilities"` // Array of ability icons in sequence
Name string `json:"name"` // Display name for this starter
Description string `json:"description"` // Description text
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
}
// HeroicOPWheel represents the wheel phase of a heroic opportunity
type HeroicOPWheel struct {
mu sync.RWMutex
ID int32 `json:"id"` // Unique identifier for this wheel
StarterLinkID int32 `json:"starter_link_id"` // ID of the starter this wheel belongs to
Order int8 `json:"order"` // 0 = unordered, 1+ = ordered
ShiftIcon int16 `json:"shift_icon"` // Icon that can shift/change the wheel
Chance float32 `json:"chance"` // Probability factor for selecting this wheel
Abilities [6]int16 `json:"abilities"` // Array of ability icons for the wheel
SpellID int32 `json:"spell_id"` // Spell cast when HO completes successfully
Name string `json:"name"` // Display name for this wheel
Description string `json:"description"` // Description text
RequiredPlayers int8 `json:"required_players"` // Minimum players required
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
mu sync.RWMutex
ID int32 `json:"id"` // Unique identifier for this wheel
StarterLinkID int32 `json:"starter_link_id"` // ID of the starter this wheel belongs to
Order int8 `json:"order"` // 0 = unordered, 1+ = ordered
ShiftIcon int16 `json:"shift_icon"` // Icon that can shift/change the wheel
Chance float32 `json:"chance"` // Probability factor for selecting this wheel
Abilities [6]int16 `json:"abilities"` // Array of ability icons for the wheel
SpellID int32 `json:"spell_id"` // Spell cast when HO completes successfully
Name string `json:"name"` // Display name for this wheel
Description string `json:"description"` // Description text
RequiredPlayers int8 `json:"required_players"` // Minimum players required
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
}
// HeroicOP represents an active heroic opportunity instance
type HeroicOP struct {
mu sync.RWMutex
ID int64 `json:"id"` // Unique instance ID
EncounterID int32 `json:"encounter_id"` // Encounter this HO belongs to
StarterID int32 `json:"starter_id"` // ID of the completed starter
WheelID int32 `json:"wheel_id"` // ID of the active wheel
State int8 `json:"state"` // Current HO state
StartTime time.Time `json:"start_time"` // When the HO started
WheelStartTime time.Time `json:"wheel_start_time"` // When wheel phase started
TimeRemaining int32 `json:"time_remaining"` // Milliseconds remaining
TotalTime int32 `json:"total_time"` // Total time allocated (ms)
Complete int8 `json:"complete"` // Completion status (0/1)
Countered [6]int8 `json:"countered"` // Which wheel abilities are completed
ShiftUsed int8 `json:"shift_used"` // Whether shift has been used
StarterProgress int8 `json:"starter_progress"` // Current position in starter chain
Participants map[int32]bool `json:"participants"` // Character IDs that participated
CurrentStarters []int32 `json:"current_starters"` // Active starter IDs during chain phase
CompletedBy int32 `json:"completed_by"` // Character ID that completed the HO
SpellName string `json:"spell_name"` // Name of completion spell
SpellDescription string `json:"spell_description"` // Description of completion spell
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
ID int64 `json:"id"` // Unique instance ID
EncounterID int32 `json:"encounter_id"` // Encounter this HO belongs to
StarterID int32 `json:"starter_id"` // ID of the completed starter
WheelID int32 `json:"wheel_id"` // ID of the active wheel
State int8 `json:"state"` // Current HO state
StartTime time.Time `json:"start_time"` // When the HO started
WheelStartTime time.Time `json:"wheel_start_time"` // When wheel phase started
TimeRemaining int32 `json:"time_remaining"` // Milliseconds remaining
TotalTime int32 `json:"total_time"` // Total time allocated (ms)
Complete int8 `json:"complete"` // Completion status (0/1)
Countered [6]int8 `json:"countered"` // Which wheel abilities are completed
ShiftUsed int8 `json:"shift_used"` // Whether shift has been used
StarterProgress int8 `json:"starter_progress"` // Current position in starter chain
Participants map[int32]bool `json:"participants"` // Character IDs that participated
CurrentStarters []int32 `json:"current_starters"` // Active starter IDs during chain phase
CompletedBy int32 `json:"completed_by"` // Character ID that completed the HO
SpellName string `json:"spell_name"` // Name of completion spell
SpellDescription string `json:"spell_description"` // Description of completion spell
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
}
// HeroicOPProgress tracks progress during starter chain phase
@ -66,28 +66,28 @@ type HeroicOPProgress struct {
// HeroicOPData represents database record structure
type HeroicOPData struct {
ID int32 `json:"id"`
HOType string `json:"ho_type"` // "Starter" or "Wheel"
StarterClass int8 `json:"starter_class"` // For starters
StarterIcon int16 `json:"starter_icon"` // For starters
StarterLinkID int32 `json:"starter_link_id"` // For wheels
ChainOrder int8 `json:"chain_order"` // For wheels
ShiftIcon int16 `json:"shift_icon"` // For wheels
SpellID int32 `json:"spell_id"` // For wheels
Chance float32 `json:"chance"` // For wheels
Ability1 int16 `json:"ability1"`
Ability2 int16 `json:"ability2"`
Ability3 int16 `json:"ability3"`
Ability4 int16 `json:"ability4"`
Ability5 int16 `json:"ability5"`
Ability6 int16 `json:"ability6"`
Name string `json:"name"`
Description string `json:"description"`
ID int32 `json:"id"`
HOType string `json:"ho_type"` // "Starter" or "Wheel"
StarterClass int8 `json:"starter_class"` // For starters
StarterIcon int16 `json:"starter_icon"` // For starters
StarterLinkID int32 `json:"starter_link_id"` // For wheels
ChainOrder int8 `json:"chain_order"` // For wheels
ShiftIcon int16 `json:"shift_icon"` // For wheels
SpellID int32 `json:"spell_id"` // For wheels
Chance float32 `json:"chance"` // For wheels
Ability1 int16 `json:"ability1"`
Ability2 int16 `json:"ability2"`
Ability3 int16 `json:"ability3"`
Ability4 int16 `json:"ability4"`
Ability5 int16 `json:"ability5"`
Ability6 int16 `json:"ability6"`
Name string `json:"name"`
Description string `json:"description"`
}
// MasterHeroicOPList manages all heroic opportunity configurations
type MasterHeroicOPList struct {
mu sync.RWMutex
mu sync.RWMutex
// Structure: map[class]map[starter_id][]wheel
starters map[int8]map[int32]*HeroicOPStarter
wheels map[int32][]*HeroicOPWheel // starter_id -> wheels
@ -97,13 +97,13 @@ type MasterHeroicOPList struct {
// HeroicOPManager manages active heroic opportunity instances
type HeroicOPManager struct {
mu sync.RWMutex
activeHOs map[int64]*HeroicOP // instance_id -> HO
encounterHOs map[int32][]*HeroicOP // encounter_id -> HOs
masterList *MasterHeroicOPList
database HeroicOPDatabase
eventHandler HeroicOPEventHandler
logger LogHandler
mu sync.RWMutex
activeHOs map[int64]*HeroicOP // instance_id -> HO
encounterHOs map[int32][]*HeroicOP // encounter_id -> HOs
masterList *MasterHeroicOPList
database HeroicOPDatabase
eventHandler HeroicOPEventHandler
logger LogHandler
nextInstanceID int64
// Configuration
defaultWheelTimer int32 // milliseconds
@ -122,17 +122,17 @@ type SpellInfo struct {
// HeroicOPStatistics tracks system usage statistics
type HeroicOPStatistics struct {
TotalHOsStarted int64 `json:"total_hos_started"`
TotalHOsCompleted int64 `json:"total_hos_completed"`
TotalHOsFailed int64 `json:"total_hos_failed"`
TotalHOsTimedOut int64 `json:"total_hos_timed_out"`
AverageCompletionTime float64 `json:"average_completion_time"` // seconds
MostUsedStarter int32 `json:"most_used_starter"`
MostUsedWheel int32 `json:"most_used_wheel"`
SuccessRate float64 `json:"success_rate"` // percentage
ShiftUsageRate float64 `json:"shift_usage_rate"` // percentage
ActiveHOCount int `json:"active_ho_count"`
ParticipationStats map[int32]int64 `json:"participation_stats"` // character_id -> HO count
TotalHOsStarted int64 `json:"total_hos_started"`
TotalHOsCompleted int64 `json:"total_hos_completed"`
TotalHOsFailed int64 `json:"total_hos_failed"`
TotalHOsTimedOut int64 `json:"total_hos_timed_out"`
AverageCompletionTime float64 `json:"average_completion_time"` // seconds
MostUsedStarter int32 `json:"most_used_starter"`
MostUsedWheel int32 `json:"most_used_wheel"`
SuccessRate float64 `json:"success_rate"` // percentage
ShiftUsageRate float64 `json:"shift_usage_rate"` // percentage
ActiveHOCount int `json:"active_ho_count"`
ParticipationStats map[int32]int64 `json:"participation_stats"` // character_id -> HO count
}
// HeroicOPSearchCriteria for searching heroic opportunities
@ -160,37 +160,37 @@ type HeroicOPEvent struct {
// PacketData represents data sent to client for HO display
type PacketData struct {
SpellName string `json:"spell_name"`
SpellDescription string `json:"spell_description"`
TimeRemaining int32 `json:"time_remaining"` // milliseconds
TotalTime int32 `json:"total_time"` // milliseconds
Abilities [6]int16 `json:"abilities"` // Current wheel abilities
Countered [6]int8 `json:"countered"` // Completion status
Complete int8 `json:"complete"` // Overall completion (0/1)
State int8 `json:"state"` // Current HO state
CanShift bool `json:"can_shift"` // Whether shift is available
ShiftIcon int16 `json:"shift_icon"` // Icon for shift ability
SpellName string `json:"spell_name"`
SpellDescription string `json:"spell_description"`
TimeRemaining int32 `json:"time_remaining"` // milliseconds
TotalTime int32 `json:"total_time"` // milliseconds
Abilities [6]int16 `json:"abilities"` // Current wheel abilities
Countered [6]int8 `json:"countered"` // Completion status
Complete int8 `json:"complete"` // Overall completion (0/1)
State int8 `json:"state"` // Current HO state
CanShift bool `json:"can_shift"` // Whether shift is available
ShiftIcon int16 `json:"shift_icon"` // Icon for shift ability
}
// PlayerHOInfo contains player-specific HO information
type PlayerHOInfo struct {
CharacterID int32 `json:"character_id"`
ParticipatingHOs []int64 `json:"participating_hos"` // HO instance IDs
LastActivity time.Time `json:"last_activity"`
TotalHOsJoined int64 `json:"total_hos_joined"`
TotalHOsCompleted int64 `json:"total_hos_completed"`
SuccessRate float64 `json:"success_rate"`
CharacterID int32 `json:"character_id"`
ParticipatingHOs []int64 `json:"participating_hos"` // HO instance IDs
LastActivity time.Time `json:"last_activity"`
TotalHOsJoined int64 `json:"total_hos_joined"`
TotalHOsCompleted int64 `json:"total_hos_completed"`
SuccessRate float64 `json:"success_rate"`
}
// Configuration structure for HO system
type HeroicOPConfig struct {
DefaultWheelTimer int32 `json:"default_wheel_timer"` // milliseconds
StarterChainTimeout int32 `json:"starter_chain_timeout"` // milliseconds
MaxConcurrentHOs int `json:"max_concurrent_hos"`
EnableLogging bool `json:"enable_logging"`
EnableStatistics bool `json:"enable_statistics"`
EnableShifting bool `json:"enable_shifting"`
RequireClassMatch bool `json:"require_class_match"` // Enforce starter class restrictions
AutoCleanupInterval int32 `json:"auto_cleanup_interval"` // seconds
MaxHistoryEntries int `json:"max_history_entries"`
DefaultWheelTimer int32 `json:"default_wheel_timer"` // milliseconds
StarterChainTimeout int32 `json:"starter_chain_timeout"` // milliseconds
MaxConcurrentHOs int `json:"max_concurrent_hos"`
EnableLogging bool `json:"enable_logging"`
EnableStatistics bool `json:"enable_statistics"`
EnableShifting bool `json:"enable_shifting"`
RequireClassMatch bool `json:"require_class_match"` // Enforce starter class restrictions
AutoCleanupInterval int32 `json:"auto_cleanup_interval"` // seconds
MaxHistoryEntries int `json:"max_history_entries"`
}

View File

@ -16,40 +16,40 @@ const (
AlignmentNeutral = 3
// Transaction types for house history
TransactionPurchase = 1
TransactionUpkeep = 2
TransactionDeposit = 3
TransactionWithdrawal = 4
TransactionAmenity = 5
TransactionPurchase = 1
TransactionUpkeep = 2
TransactionDeposit = 3
TransactionWithdrawal = 4
TransactionAmenity = 5
TransactionVaultExpansion = 6
TransactionRent = 7
TransactionForeclosure = 8
TransactionTransfer = 9
TransactionRepair = 10
TransactionRent = 7
TransactionForeclosure = 8
TransactionTransfer = 9
TransactionRepair = 10
// History position flags
HistoryFlagPositive = 1
HistoryFlagNegative = 0
// Upkeep periods (in seconds)
UpkeepPeriodWeekly = 604800 // 7 days
UpkeepPeriodMonthly = 2592000 // 30 days
UpkeepGracePeriod = 259200 // 3 days
UpkeepPeriodWeekly = 604800 // 7 days
UpkeepPeriodMonthly = 2592000 // 30 days
UpkeepGracePeriod = 259200 // 3 days
// House status flags
HouseStatusActive = 0
HouseStatusUpkeepDue = 1
HouseStatusForeclosed = 2
HouseStatusAbandoned = 3
HouseStatusActive = 0
HouseStatusUpkeepDue = 1
HouseStatusForeclosed = 2
HouseStatusAbandoned = 3
// Maximum values
MaxHouseName = 64
MaxReasonLength = 255
MaxDepositHistory = 100
MaxHouseName = 64
MaxReasonLength = 255
MaxDepositHistory = 100
MaxTransactionHistory = 500
MaxVaultSlots = 200
MaxAmenities = 50
MaxAccessEntries = 100
MaxVaultSlots = 200
MaxAmenities = 50
MaxAccessEntries = 100
// Database retry settings
MaxDatabaseRetries = 3
@ -60,25 +60,25 @@ const (
MaxEscrowStatus = 10000000 // 10 million status
// Visit permissions
VisitPermissionPublic = 0
VisitPermissionFriends = 1
VisitPermissionGuild = 2
VisitPermissionPublic = 0
VisitPermissionFriends = 1
VisitPermissionGuild = 2
VisitPermissionInviteOnly = 3
VisitPermissionPrivate = 4
VisitPermissionPrivate = 4
// Housing opcodes/packet types
OpHousePurchase = "PlayerHousePurchase"
OpHousingList = "CharacterHousingList"
OpBaseHouseWindow = "PlayerHouseBaseScreen"
OpHouseVisitWindow = "PlayerHouseVisit"
OpBuyHouse = "BuyHouse"
OpEnterHouse = "EnterHouse"
OpUpdateHouseAccess = "UpdateHouseAccessDataMsg"
OpHouseDeposit = "HouseDeposit"
OpHouseWithdrawal = "HouseWithdrawal"
OpPlaceItem = "PlaceItemInHouse"
OpRemoveItem = "RemoveItemFromHouse"
OpUpdateAmenities = "UpdateHouseAmenities"
OpHousePurchase = "PlayerHousePurchase"
OpHousingList = "CharacterHousingList"
OpBaseHouseWindow = "PlayerHouseBaseScreen"
OpHouseVisitWindow = "PlayerHouseVisit"
OpBuyHouse = "BuyHouse"
OpEnterHouse = "EnterHouse"
OpUpdateHouseAccess = "UpdateHouseAccessDataMsg"
OpHouseDeposit = "HouseDeposit"
OpHouseWithdrawal = "HouseWithdrawal"
OpPlaceItem = "PlaceItemInHouse"
OpRemoveItem = "RemoveItemFromHouse"
OpUpdateAmenities = "UpdateHouseAmenities"
// Error messages
ErrHouseNotFound = "house not found"
@ -95,17 +95,17 @@ const (
ErrMaxHousesReached = "maximum number of houses reached"
// Default upkeep costs (can be overridden per house type)
DefaultUpkeepCoins = 10000 // 1 gold
DefaultUpkeepCoins = 10000 // 1 gold
DefaultUpkeepStatus = 100
// Item placement constants
MaxItemsPerHouse = 1000
MaxItemStackSize = 100
ItemPlacementRadius = 50.0 // Maximum distance from spawn point
MaxItemsPerHouse = 1000
MaxItemStackSize = 100
ItemPlacementRadius = 50.0 // Maximum distance from spawn point
// House zones configuration
DefaultInstanceLifetime = 3600 // 1 hour in seconds
MaxHouseVisitors = 50 // Maximum concurrent visitors
MaxHouseVisitors = 50 // Maximum concurrent visitors
// Amenity types
AmenityVaultExpansion = 1
@ -118,8 +118,8 @@ const (
AmenityTeleporter = 8
// Foreclosure settings
ForeclosureWarningDays = 7 // Days before foreclosure
ForeclosureNoticeDays = 3 // Days of final notice
ForeclosureWarningDays = 7 // Days before foreclosure
ForeclosureNoticeDays = 3 // Days of final notice
// Deposit limits
MinDepositAmount = 1
@ -132,28 +132,28 @@ const (
// House type constants for common house types
const (
HouseTypeInn = 1
HouseTypeCottage = 2
HouseTypeApartment = 3
HouseTypeHouse = 4
HouseTypeMansion = 5
HouseTypeKeep = 6
HouseTypeGuildHall = 7
HouseTypeInn = 1
HouseTypeCottage = 2
HouseTypeApartment = 3
HouseTypeHouse = 4
HouseTypeMansion = 5
HouseTypeKeep = 6
HouseTypeGuildHall = 7
HouseTypePrestigeHome = 8
)
// Access permission flags (bitwise)
const (
PermissionEnter = 1 << iota // Can enter the house
PermissionPlace = 1 << iota // Can place items
PermissionRemove = 1 << iota // Can remove items
PermissionMove = 1 << iota // Can move items
PermissionVault = 1 << iota // Can access vault
PermissionDeposit = 1 << iota // Can make deposits
PermissionWithdraw = 1 << iota // Can make withdrawals
PermissionInvite = 1 << iota // Can invite others
PermissionKick = 1 << iota // Can kick visitors
PermissionAdmin = 1 << iota // Full administrative access
PermissionEnter = 1 << iota // Can enter the house
PermissionPlace = 1 << iota // Can place items
PermissionRemove = 1 << iota // Can remove items
PermissionMove = 1 << iota // Can move items
PermissionVault = 1 << iota // Can access vault
PermissionDeposit = 1 << iota // Can make deposits
PermissionWithdraw = 1 << iota // Can make withdrawals
PermissionInvite = 1 << iota // Can invite others
PermissionKick = 1 << iota // Can kick visitors
PermissionAdmin = 1 << iota // Full administrative access
)
// Default permission sets
@ -220,13 +220,13 @@ var HouseTypeNames = map[int]string{
// Default costs for house types (in copper coins)
var DefaultHouseCosts = map[int]int64{
HouseTypeInn: 50000, // 5 gold
HouseTypeCottage: 200000, // 20 gold
HouseTypeApartment: 500000, // 50 gold
HouseTypeHouse: 1000000, // 100 gold
HouseTypeMansion: 5000000, // 500 gold
HouseTypeKeep: 10000000, // 1000 gold
HouseTypeGuildHall: 50000000, // 5000 gold
HouseTypeInn: 50000, // 5 gold
HouseTypeCottage: 200000, // 20 gold
HouseTypeApartment: 500000, // 50 gold
HouseTypeHouse: 1000000, // 100 gold
HouseTypeMansion: 5000000, // 500 gold
HouseTypeKeep: 10000000, // 1000 gold
HouseTypeGuildHall: 50000000, // 5000 gold
HouseTypePrestigeHome: 100000000, // 10000 gold
}
@ -244,13 +244,13 @@ var DefaultHouseStatusCosts = map[int]int64{
// Default upkeep costs (in copper coins per week)
var DefaultHouseUpkeepCosts = map[int]int64{
HouseTypeInn: 5000, // 50 silver
HouseTypeCottage: 10000, // 1 gold
HouseTypeApartment: 25000, // 2.5 gold
HouseTypeHouse: 50000, // 5 gold
HouseTypeMansion: 100000, // 10 gold
HouseTypeKeep: 200000, // 20 gold
HouseTypeGuildHall: 500000, // 50 gold
HouseTypeInn: 5000, // 50 silver
HouseTypeCottage: 10000, // 1 gold
HouseTypeApartment: 25000, // 2.5 gold
HouseTypeHouse: 50000, // 5 gold
HouseTypeMansion: 100000, // 10 gold
HouseTypeKeep: 200000, // 20 gold
HouseTypeGuildHall: 500000, // 50 gold
HouseTypePrestigeHome: 1000000, // 100 gold
}

View File

@ -985,8 +985,8 @@ func (dhm *DatabaseHousingManager) GetHouseStatistics(ctx context.Context) (*Hou
// Get basic counts
queries := map[string]*int64{
"SELECT COUNT(*) FROM character_houses": &stats.TotalHouses,
"SELECT COUNT(*) FROM character_houses WHERE status = 0": &stats.ActiveHouses,
"SELECT COUNT(*) FROM character_houses WHERE status = 2": &stats.ForelosedHouses,
"SELECT COUNT(*) FROM character_houses WHERE status = 0": &stats.ActiveHouses,
"SELECT COUNT(*) FROM character_houses WHERE status = 2": &stats.ForelosedHouses,
"SELECT COUNT(*) FROM character_house_deposits": &stats.TotalDeposits,
"SELECT COUNT(*) FROM character_house_history WHERE pos_flag = 0": &stats.TotalWithdrawals,
}

View File

@ -210,14 +210,14 @@ type ZoneInfo struct {
// HouseInstance contains active house instance information
type HouseInstance struct {
InstanceID int32 `json:"instance_id"`
HouseID int64 `json:"house_id"`
OwnerID int32 `json:"owner_id"`
ZoneID int32 `json:"zone_id"`
CreatedTime time.Time `json:"created_time"`
LastActivity time.Time `json:"last_activity"`
CurrentVisitors []int32 `json:"current_visitors"`
IsActive bool `json:"is_active"`
InstanceID int32 `json:"instance_id"`
HouseID int64 `json:"house_id"`
OwnerID int32 `json:"owner_id"`
ZoneID int32 `json:"zone_id"`
CreatedTime time.Time `json:"created_time"`
LastActivity time.Time `json:"last_activity"`
CurrentVisitors []int32 `json:"current_visitors"`
IsActive bool `json:"is_active"`
}
// Adapter interfaces for integration with existing systems

View File

@ -7,22 +7,22 @@ import (
// HouseZone represents a house type that can be purchased
type HouseZone struct {
mu sync.RWMutex
ID int32 `json:"id"` // Unique house type identifier
Name string `json:"name"` // House name/type
ZoneID int32 `json:"zone_id"` // Zone where house is located
CostCoin int64 `json:"cost_coin"` // Purchase cost in coins
CostStatus int64 `json:"cost_status"` // Purchase cost in status points
UpkeepCoin int64 `json:"upkeep_coin"` // Upkeep cost in coins
UpkeepStatus int64 `json:"upkeep_status"` // Upkeep cost in status points
Alignment int8 `json:"alignment"` // Alignment requirement
GuildLevel int8 `json:"guild_level"` // Required guild level
VaultSlots int `json:"vault_slots"` // Number of vault storage slots
MaxItems int `json:"max_items"` // Maximum items that can be placed
MaxVisitors int `json:"max_visitors"` // Maximum concurrent visitors
UpkeepPeriod int32 `json:"upkeep_period"` // Upkeep period in seconds
Description string `json:"description"` // Description text
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
mu sync.RWMutex
ID int32 `json:"id"` // Unique house type identifier
Name string `json:"name"` // House name/type
ZoneID int32 `json:"zone_id"` // Zone where house is located
CostCoin int64 `json:"cost_coin"` // Purchase cost in coins
CostStatus int64 `json:"cost_status"` // Purchase cost in status points
UpkeepCoin int64 `json:"upkeep_coin"` // Upkeep cost in coins
UpkeepStatus int64 `json:"upkeep_status"` // Upkeep cost in status points
Alignment int8 `json:"alignment"` // Alignment requirement
GuildLevel int8 `json:"guild_level"` // Required guild level
VaultSlots int `json:"vault_slots"` // Number of vault storage slots
MaxItems int `json:"max_items"` // Maximum items that can be placed
MaxVisitors int `json:"max_visitors"` // Maximum concurrent visitors
UpkeepPeriod int32 `json:"upkeep_period"` // Upkeep period in seconds
Description string `json:"description"` // Description text
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
}
// PlayerHouse represents a house owned by a player
@ -48,13 +48,13 @@ type PlayerHouse struct {
// HouseDeposit represents a deposit transaction
type HouseDeposit struct {
Timestamp time.Time `json:"timestamp"` // When deposit was made
Amount int64 `json:"amount"` // Coin amount deposited
LastAmount int64 `json:"last_amount"` // Previous coin amount
Status int64 `json:"status"` // Status points deposited
LastStatus int64 `json:"last_status"` // Previous status points
Name string `json:"name"` // Player who made deposit
CharacterID int32 `json:"character_id"` // Character ID who made deposit
Timestamp time.Time `json:"timestamp"` // When deposit was made
Amount int64 `json:"amount"` // Coin amount deposited
LastAmount int64 `json:"last_amount"` // Previous coin amount
Status int64 `json:"status"` // Status points deposited
LastStatus int64 `json:"last_status"` // Previous status points
Name string `json:"name"` // Player who made deposit
CharacterID int32 `json:"character_id"` // Character ID who made deposit
}
// HouseHistory represents a house transaction history entry
@ -71,99 +71,99 @@ type HouseHistory struct {
// HouseAccess represents access permissions for a player
type HouseAccess struct {
CharacterID int32 `json:"character_id"` // Character being granted access
PlayerName string `json:"player_name"` // Player name
AccessLevel int8 `json:"access_level"` // Access level
Permissions int32 `json:"permissions"` // Permission flags
GrantedBy int32 `json:"granted_by"` // Who granted the access
GrantedDate time.Time `json:"granted_date"` // When access was granted
ExpiresDate time.Time `json:"expires_date"` // When access expires (0 = never)
Notes string `json:"notes"` // Optional notes
CharacterID int32 `json:"character_id"` // Character being granted access
PlayerName string `json:"player_name"` // Player name
AccessLevel int8 `json:"access_level"` // Access level
Permissions int32 `json:"permissions"` // Permission flags
GrantedBy int32 `json:"granted_by"` // Who granted the access
GrantedDate time.Time `json:"granted_date"` // When access was granted
ExpiresDate time.Time `json:"expires_date"` // When access expires (0 = never)
Notes string `json:"notes"` // Optional notes
}
// HouseAmenity represents a purchased house amenity
type HouseAmenity struct {
ID int32 `json:"id"` // Amenity ID
Type int `json:"type"` // Amenity type
Name string `json:"name"` // Amenity name
Cost int64 `json:"cost"` // Purchase cost
StatusCost int64 `json:"status_cost"` // Status cost
PurchaseDate time.Time `json:"purchase_date"` // When purchased
X float32 `json:"x"` // X position
Y float32 `json:"y"` // Y position
Z float32 `json:"z"` // Z position
Heading float32 `json:"heading"` // Heading
IsActive bool `json:"is_active"` // Whether amenity is active
ID int32 `json:"id"` // Amenity ID
Type int `json:"type"` // Amenity type
Name string `json:"name"` // Amenity name
Cost int64 `json:"cost"` // Purchase cost
StatusCost int64 `json:"status_cost"` // Status cost
PurchaseDate time.Time `json:"purchase_date"` // When purchased
X float32 `json:"x"` // X position
Y float32 `json:"y"` // Y position
Z float32 `json:"z"` // Z position
Heading float32 `json:"heading"` // Heading
IsActive bool `json:"is_active"` // Whether amenity is active
}
// HouseItem represents an item placed in a house
type HouseItem struct {
ID int64 `json:"id"` // Item unique ID
ItemID int32 `json:"item_id"` // Item template ID
CharacterID int32 `json:"character_id"` // Who placed the item
X float32 `json:"x"` // X position
Y float32 `json:"y"` // Y position
Z float32 `json:"z"` // Z position
Heading float32 `json:"heading"` // Heading
PitchX float32 `json:"pitch_x"` // Pitch X
PitchY float32 `json:"pitch_y"` // Pitch Y
RollX float32 `json:"roll_x"` // Roll X
RollY float32 `json:"roll_y"` // Roll Y
PlacedDate time.Time `json:"placed_date"` // When item was placed
Quantity int32 `json:"quantity"` // Item quantity
Condition int8 `json:"condition"` // Item condition
House string `json:"house"` // House identifier
ID int64 `json:"id"` // Item unique ID
ItemID int32 `json:"item_id"` // Item template ID
CharacterID int32 `json:"character_id"` // Who placed the item
X float32 `json:"x"` // X position
Y float32 `json:"y"` // Y position
Z float32 `json:"z"` // Z position
Heading float32 `json:"heading"` // Heading
PitchX float32 `json:"pitch_x"` // Pitch X
PitchY float32 `json:"pitch_y"` // Pitch Y
RollX float32 `json:"roll_x"` // Roll X
RollY float32 `json:"roll_y"` // Roll Y
PlacedDate time.Time `json:"placed_date"` // When item was placed
Quantity int32 `json:"quantity"` // Item quantity
Condition int8 `json:"condition"` // Item condition
House string `json:"house"` // House identifier
}
// HouseSettings represents house configuration settings
type HouseSettings struct {
HouseName string `json:"house_name"` // Custom house name
VisitPermission int8 `json:"visit_permission"` // Who can visit
PublicNote string `json:"public_note"` // Public note displayed
PrivateNote string `json:"private_note"` // Private note for owner
AllowFriends bool `json:"allow_friends"` // Allow friends to visit
AllowGuild bool `json:"allow_guild"` // Allow guild members to visit
RequireApproval bool `json:"require_approval"` // Require approval for visits
ShowOnDirectory bool `json:"show_on_directory"` // Show in house directory
AllowDecoration bool `json:"allow_decoration"` // Allow others to decorate
TaxExempt bool `json:"tax_exempt"` // Tax exemption status
HouseName string `json:"house_name"` // Custom house name
VisitPermission int8 `json:"visit_permission"` // Who can visit
PublicNote string `json:"public_note"` // Public note displayed
PrivateNote string `json:"private_note"` // Private note for owner
AllowFriends bool `json:"allow_friends"` // Allow friends to visit
AllowGuild bool `json:"allow_guild"` // Allow guild members to visit
RequireApproval bool `json:"require_approval"` // Require approval for visits
ShowOnDirectory bool `json:"show_on_directory"` // Show in house directory
AllowDecoration bool `json:"allow_decoration"` // Allow others to decorate
TaxExempt bool `json:"tax_exempt"` // Tax exemption status
}
// HousingManager manages the overall housing system
type HousingManager struct {
mu sync.RWMutex
houseZones map[int32]*HouseZone // Available house types
playerHouses map[int64]*PlayerHouse // All player houses by unique ID
characterHouses map[int32][]*PlayerHouse // Houses by character ID
zoneInstances map[int32]map[int32]*PlayerHouse // Houses by zone and instance
database HousingDatabase
clientManager ClientManager
playerManager PlayerManager
itemManager ItemManager
zoneManager ZoneManager
eventHandler HousingEventHandler
logger LogHandler
mu sync.RWMutex
houseZones map[int32]*HouseZone // Available house types
playerHouses map[int64]*PlayerHouse // All player houses by unique ID
characterHouses map[int32][]*PlayerHouse // Houses by character ID
zoneInstances map[int32]map[int32]*PlayerHouse // Houses by zone and instance
database HousingDatabase
clientManager ClientManager
playerManager PlayerManager
itemManager ItemManager
zoneManager ZoneManager
eventHandler HousingEventHandler
logger LogHandler
// Configuration
enableUpkeep bool
enableForeclosure bool
upkeepGracePeriod int32
enableUpkeep bool
enableForeclosure bool
upkeepGracePeriod int32
maxHousesPerPlayer int
enableStatistics bool
enableStatistics bool
}
// HousingStatistics tracks housing system usage
type HousingStatistics struct {
TotalHouses int64 `json:"total_houses"`
ActiveHouses int64 `json:"active_houses"`
ForelosedHouses int64 `json:"foreclosed_houses"`
TotalDeposits int64 `json:"total_deposits"`
TotalWithdrawals int64 `json:"total_withdrawals"`
AverageUpkeepPaid float64 `json:"average_upkeep_paid"`
MostPopularHouseType int32 `json:"most_popular_house_type"`
HousesByType map[int32]int64 `json:"houses_by_type"`
HousesByAlignment map[int8]int64 `json:"houses_by_alignment"`
RevenueByType map[int]int64 `json:"revenue_by_type"`
TopDepositors []PlayerDeposits `json:"top_depositors"`
TotalHouses int64 `json:"total_houses"`
ActiveHouses int64 `json:"active_houses"`
ForelosedHouses int64 `json:"foreclosed_houses"`
TotalDeposits int64 `json:"total_deposits"`
TotalWithdrawals int64 `json:"total_withdrawals"`
AverageUpkeepPaid float64 `json:"average_upkeep_paid"`
MostPopularHouseType int32 `json:"most_popular_house_type"`
HousesByType map[int32]int64 `json:"houses_by_type"`
HousesByAlignment map[int8]int64 `json:"houses_by_alignment"`
RevenueByType map[int]int64 `json:"revenue_by_type"`
TopDepositors []PlayerDeposits `json:"top_depositors"`
}
// PlayerDeposits tracks deposits by player
@ -176,17 +176,17 @@ type PlayerDeposits struct {
// HousingSearchCriteria for searching houses
type HousingSearchCriteria struct {
OwnerName string `json:"owner_name"` // Filter by owner name
HouseType int32 `json:"house_type"` // Filter by house type
Alignment int8 `json:"alignment"` // Filter by alignment
MinCost int64 `json:"min_cost"` // Minimum cost filter
MaxCost int64 `json:"max_cost"` // Maximum cost filter
Zone int32 `json:"zone"` // Filter by zone
VisitableOnly bool `json:"visitable_only"` // Only houses that can be visited
PublicOnly bool `json:"public_only"` // Only publicly accessible houses
NamePattern string `json:"name_pattern"` // Filter by house name pattern
HasAmenities bool `json:"has_amenities"` // Filter houses with amenities
MinVaultSlots int `json:"min_vault_slots"` // Minimum vault slots
OwnerName string `json:"owner_name"` // Filter by owner name
HouseType int32 `json:"house_type"` // Filter by house type
Alignment int8 `json:"alignment"` // Filter by alignment
MinCost int64 `json:"min_cost"` // Minimum cost filter
MaxCost int64 `json:"max_cost"` // Maximum cost filter
Zone int32 `json:"zone"` // Filter by zone
VisitableOnly bool `json:"visitable_only"` // Only houses that can be visited
PublicOnly bool `json:"public_only"` // Only publicly accessible houses
NamePattern string `json:"name_pattern"` // Filter by house name pattern
HasAmenities bool `json:"has_amenities"` // Filter houses with amenities
MinVaultSlots int `json:"min_vault_slots"` // Minimum vault slots
}
// Database record types for data persistence
@ -211,36 +211,36 @@ type HouseZoneData struct {
// PlayerHouseData represents database record for player houses
type PlayerHouseData struct {
UniqueID int64 `json:"unique_id"`
CharacterID int32 `json:"char_id"`
HouseID int32 `json:"house_id"`
InstanceID int32 `json:"instance_id"`
UpkeepDue time.Time `json:"upkeep_due"`
EscrowCoins int64 `json:"escrow_coins"`
EscrowStatus int64 `json:"escrow_status"`
Status int8 `json:"status"`
HouseName string `json:"house_name"`
VisitPermission int8 `json:"visit_permission"`
PublicNote string `json:"public_note"`
PrivateNote string `json:"private_note"`
AllowFriends bool `json:"allow_friends"`
AllowGuild bool `json:"allow_guild"`
RequireApproval bool `json:"require_approval"`
ShowOnDirectory bool `json:"show_on_directory"`
AllowDecoration bool `json:"allow_decoration"`
TaxExempt bool `json:"tax_exempt"`
UniqueID int64 `json:"unique_id"`
CharacterID int32 `json:"char_id"`
HouseID int32 `json:"house_id"`
InstanceID int32 `json:"instance_id"`
UpkeepDue time.Time `json:"upkeep_due"`
EscrowCoins int64 `json:"escrow_coins"`
EscrowStatus int64 `json:"escrow_status"`
Status int8 `json:"status"`
HouseName string `json:"house_name"`
VisitPermission int8 `json:"visit_permission"`
PublicNote string `json:"public_note"`
PrivateNote string `json:"private_note"`
AllowFriends bool `json:"allow_friends"`
AllowGuild bool `json:"allow_guild"`
RequireApproval bool `json:"require_approval"`
ShowOnDirectory bool `json:"show_on_directory"`
AllowDecoration bool `json:"allow_decoration"`
TaxExempt bool `json:"tax_exempt"`
}
// HouseDepositData represents database record for deposits
type HouseDepositData struct {
HouseID int64 `json:"house_id"`
Timestamp time.Time `json:"timestamp"`
Amount int64 `json:"amount"`
LastAmount int64 `json:"last_amount"`
Status int64 `json:"status"`
LastStatus int64 `json:"last_status"`
Name string `json:"name"`
CharacterID int32 `json:"character_id"`
HouseID int64 `json:"house_id"`
Timestamp time.Time `json:"timestamp"`
Amount int64 `json:"amount"`
LastAmount int64 `json:"last_amount"`
Status int64 `json:"status"`
LastStatus int64 `json:"last_status"`
Name string `json:"name"`
CharacterID int32 `json:"character_id"`
}
// HouseHistoryData represents database record for house history
@ -258,15 +258,15 @@ type HouseHistoryData struct {
// HouseAccessData represents database record for house access
type HouseAccessData struct {
HouseID int64 `json:"house_id"`
CharacterID int32 `json:"character_id"`
PlayerName string `json:"player_name"`
AccessLevel int8 `json:"access_level"`
Permissions int32 `json:"permissions"`
GrantedBy int32 `json:"granted_by"`
GrantedDate time.Time `json:"granted_date"`
ExpiresDate time.Time `json:"expires_date"`
Notes string `json:"notes"`
HouseID int64 `json:"house_id"`
CharacterID int32 `json:"character_id"`
PlayerName string `json:"player_name"`
AccessLevel int8 `json:"access_level"`
Permissions int32 `json:"permissions"`
GrantedBy int32 `json:"granted_by"`
GrantedDate time.Time `json:"granted_date"`
ExpiresDate time.Time `json:"expires_date"`
Notes string `json:"notes"`
}
// HouseAmenityData represents database record for house amenities
@ -328,24 +328,24 @@ type HouseListPacketData struct {
// PlayerHouseInfo represents house info for list display
type PlayerHouseInfo struct {
UniqueID int64 `json:"unique_id"`
Name string `json:"name"`
HouseType string `json:"house_type"`
UniqueID int64 `json:"unique_id"`
Name string `json:"name"`
HouseType string `json:"house_type"`
UpkeepDue time.Time `json:"upkeep_due"`
EscrowCoins int64 `json:"escrow_coins"`
EscrowStatus int64 `json:"escrow_status"`
Status int8 `json:"status"`
CanEnter bool `json:"can_enter"`
EscrowCoins int64 `json:"escrow_coins"`
EscrowStatus int64 `json:"escrow_status"`
Status int8 `json:"status"`
CanEnter bool `json:"can_enter"`
}
// BaseHouseWindowPacketData represents data for main house management UI
type BaseHouseWindowPacketData struct {
HouseInfo PlayerHouseInfo `json:"house_info"`
HouseInfo PlayerHouseInfo `json:"house_info"`
RecentDeposits []HouseDeposit `json:"recent_deposits"`
RecentHistory []HouseHistory `json:"recent_history"`
Amenities []HouseAmenity `json:"amenities"`
Settings HouseSettings `json:"settings"`
CanManage bool `json:"can_manage"`
Amenities []HouseAmenity `json:"amenities"`
Settings HouseSettings `json:"settings"`
CanManage bool `json:"can_manage"`
}
// HouseVisitPacketData represents data for house visit UI
@ -355,13 +355,13 @@ type HouseVisitPacketData struct {
// VisitableHouse represents a house that can be visited
type VisitableHouse struct {
UniqueID int64 `json:"unique_id"`
OwnerName string `json:"owner_name"`
HouseName string `json:"house_name"`
HouseType string `json:"house_type"`
PublicNote string `json:"public_note"`
CanVisit bool `json:"can_visit"`
RequiresApproval bool `json:"requires_approval"`
UniqueID int64 `json:"unique_id"`
OwnerName string `json:"owner_name"`
HouseName string `json:"house_name"`
HouseType string `json:"house_type"`
PublicNote string `json:"public_note"`
CanVisit bool `json:"can_visit"`
RequiresApproval bool `json:"requires_approval"`
}
// Event structures for housing system events
@ -378,13 +378,13 @@ type HousingEvent struct {
// Configuration structure for housing system
type HousingConfig struct {
EnableUpkeep bool `json:"enable_upkeep"`
EnableForeclosure bool `json:"enable_foreclosure"`
UpkeepGracePeriod int32 `json:"upkeep_grace_period"` // seconds
MaxHousesPerPlayer int `json:"max_houses_per_player"`
EnableStatistics bool `json:"enable_statistics"`
AutoCleanupInterval int32 `json:"auto_cleanup_interval"` // seconds
MaxHistoryEntries int `json:"max_history_entries"`
MaxDepositEntries int `json:"max_deposit_entries"`
EnableUpkeep bool `json:"enable_upkeep"`
EnableForeclosure bool `json:"enable_foreclosure"`
UpkeepGracePeriod int32 `json:"upkeep_grace_period"` // seconds
MaxHousesPerPlayer int `json:"max_houses_per_player"`
EnableStatistics bool `json:"enable_statistics"`
AutoCleanupInterval int32 `json:"auto_cleanup_interval"` // seconds
MaxHistoryEntries int `json:"max_history_entries"`
MaxDepositEntries int `json:"max_deposit_entries"`
DefaultInstanceLifetime int32 `json:"default_instance_lifetime"` // seconds
}

686
internal/items/constants.go Normal file
View File

@ -0,0 +1,686 @@
package items
// Equipment slot constants
const (
BaseEquipment = 0
AppearanceEquipment = 1
MaxEquipment = 2 // max iterations for equipment (base is 0, appearance is 1)
)
// EQ2 slot positions (array indices)
const (
EQ2PrimarySlot = 0
EQ2SecondarySlot = 1
EQ2HeadSlot = 2
EQ2ChestSlot = 3
EQ2ShouldersSlot = 4
EQ2ForearmsSlot = 5
EQ2HandsSlot = 6
EQ2LegsSlot = 7
EQ2FeetSlot = 8
EQ2LRingSlot = 9
EQ2RRingSlot = 10
EQ2EarsSlot1 = 11
EQ2EarsSlot2 = 12
EQ2NeckSlot = 13
EQ2LWristSlot = 14
EQ2RWristSlot = 15
EQ2RangeSlot = 16
EQ2AmmoSlot = 17
EQ2WaistSlot = 18
EQ2CloakSlot = 19
EQ2CharmSlot1 = 20
EQ2CharmSlot2 = 21
EQ2FoodSlot = 22
EQ2DrinkSlot = 23
EQ2TexturesSlot = 24
EQ2HairSlot = 25
EQ2BeardSlot = 26
EQ2WingsSlot = 27
EQ2NakedChestSlot = 28
EQ2NakedLegsSlot = 29
EQ2BackSlot = 30
)
// Original slot positions (for older clients)
const (
EQ2OrigFoodSlot = 18
EQ2OrigDrinkSlot = 19
EQ2DoFCharmSlot1 = 18
EQ2DoFCharmSlot2 = 19
EQ2DoFFoodSlot = 20
EQ2DoFDrinkSlot = 21
)
// Slot bitmasks for equipment validation
const (
PrimarySlot = 1
SecondarySlot = 2
HeadSlot = 4
ChestSlot = 8
ShouldersSlot = 16
ForearmsSlot = 32
HandsSlot = 64
LegsSlot = 128
FeetSlot = 256
LRingSlot = 512
RRingSlot = 1024
EarsSlot1 = 2048
EarsSlot2 = 4096
NeckSlot = 8192
LWristSlot = 16384
RWristSlot = 32768
RangeSlot = 65536
AmmoSlot = 131072
WaistSlot = 262144
CloakSlot = 524288
CharmSlot1 = 1048576
CharmSlot2 = 2097152
FoodSlot = 4194304
DrinkSlot = 8388608
TexturesSlot = 16777216
HairSlot = 33554432
BeardSlot = 67108864
WingsSlot = 134217728
NakedChestSlot = 268435456
NakedLegsSlot = 536870912
BackSlot = 1073741824
OrigFoodSlot = 524288
OrigDrinkSlot = 1048576
DoFFoodSlot = 1048576
DoFDrinkSlot = 2097152
)
// Inventory slot limits and constants
const (
ClassicEQMaxBagSlots = 20
DoFEQMaxBagSlots = 36
NumBankSlots = 12
NumSharedBankSlots = 8
ClassicNumSlots = 22
NumSlots = 25
NumInvSlots = 6
InvSlot1 = 0
InvSlot2 = 50
InvSlot3 = 100
InvSlot4 = 150
InvSlot5 = 200
InvSlot6 = 250
BankSlot1 = 1000
BankSlot2 = 1100
BankSlot3 = 1200
BankSlot4 = 1300
BankSlot5 = 1400
BankSlot6 = 1500
BankSlot7 = 1600
BankSlot8 = 1700
)
// Item flags (bitmask values)
const (
Attuned = 1
Attuneable = 2
Artifact = 4
Lore = 8
Temporary = 16
NoTrade = 32
NoValue = 64
NoZone = 128
NoDestroy = 256
Crafted = 512
GoodOnly = 1024
EvilOnly = 2048
StackLore = 4096
LoreEquip = 8192
NoTransmute = 16384
Cursed = 32768
)
// Item flags2 (bitmask values)
const (
Ornate = 1
Heirloom = 2
AppearanceOnly = 4
Unlocked = 8
Reforged = 16
NoRepair = 32
Ethereal = 64
Refined = 128
NoSalvage = 256
Indestructible = 512
NoExperiment = 1024
HouseLore = 2048
Flags24096 = 4096 // AoM: not used at this time
BuildingBlock = 8192
FreeReforge = 16384
Flags232768 = 32768 // AoM: not used at this time
)
// Item wield types
const (
ItemWieldTypeDual = 1
ItemWieldTypeSingle = 2
ItemWieldTypeTwoHand = 4
)
// Item types
const (
ItemTypeNormal = 0
ItemTypeWeapon = 1
ItemTypeRanged = 2
ItemTypeArmor = 3
ItemTypeShield = 4
ItemTypeBag = 5
ItemTypeSkill = 6
ItemTypeRecipe = 7
ItemTypeFood = 8
ItemTypeBauble = 9
ItemTypeHouse = 10
ItemTypeThrown = 11
ItemTypeHouseContainer = 12
ItemTypeAdornment = 13
ItemTypeGenericAdornment = 14
ItemTypeProfile = 16
ItemTypePattern = 17
ItemTypeArmorset = 18
ItemTypeItemcrate = 18
ItemTypeBook = 19
ItemTypeDecoration = 20
ItemTypeDungeonMaker = 21
ItemTypeMarketplace = 22
)
// Item menu types (bitmask values)
const (
ItemMenuTypeGeneric = 1
ItemMenuTypeEquip = 2
ItemMenuTypeBag = 4
ItemMenuTypeHouse = 8
ItemMenuTypeEmptyBag = 16
ItemMenuTypeScribe = 32
ItemMenuTypeBankBag = 64
ItemMenuTypeInsufficientKnowledge = 128
ItemMenuTypeActivate = 256
ItemMenuTypeBroken = 512
ItemMenuTypeTwoHanded = 1024
ItemMenuTypeAttuned = 2048
ItemMenuTypeAttuneable = 4096
ItemMenuTypeBook = 8192
ItemMenuTypeDisplayCharges = 16384
ItemMenuTypeTest1 = 32768
ItemMenuTypeNamepet = 65536
ItemMenuTypeMentored = 131072
ItemMenuTypeConsume = 262144
ItemMenuTypeUse = 524288
ItemMenuTypeConsumeOff = 1048576
ItemMenuTypeTest3 = 1310720
ItemMenuTypeTest4 = 2097152
ItemMenuTypeTest5 = 4194304
ItemMenuTypeTest6 = 8388608
ItemMenuTypeTest7 = 16777216
ItemMenuTypeTest8 = 33554432
ItemMenuTypeTest9 = 67108864
ItemMenuTypeDamaged = 134217728
ItemMenuTypeBroken2 = 268435456
ItemMenuTypeRedeem = 536870912
ItemMenuTypeTest10 = 1073741824
ItemMenuTypeUnpack = 2147483648
)
// Original item menu types
const (
OrigItemMenuTypeFood = 2048
OrigItemMenuTypeDrink = 4096
OrigItemMenuTypeAttuned = 8192
OrigItemMenuTypeAttuneable = 16384
OrigItemMenuTypeBook = 32768
OrigItemMenuTypeStackable = 65536
OrigItemMenuTypeNamepet = 262144
)
// Item menu type2 flags
const (
ItemMenuType2Test1 = 1
ItemMenuType2Test2 = 2
ItemMenuType2Unpack = 4
ItemMenuType2Test4 = 8
ItemMenuType2Test5 = 16
ItemMenuType2Test6 = 32
ItemMenuType2Test7 = 64
ItemMenuType2Test8 = 128
ItemMenuType2Test9 = 256
ItemMenuType2Test10 = 512
ItemMenuType2Test11 = 1024
ItemMenuType2Test12 = 2048
ItemMenuType2Test13 = 4096
ItemMenuType2Test14 = 8192
ItemMenuType2Test15 = 16384
ItemMenuType2Test16 = 32768
)
// Item tier tags
const (
ItemTagCommon = 2
ItemTagUncommon = 3
ItemTagTreasured = 4
ItemTagLegendary = 7
ItemTagFabled = 9
ItemTagMythical = 12
)
// Broker type flags
const (
ItemBrokerTypeAny = 0xFFFFFFFF
ItemBrokerTypeAny64Bit = 0xFFFFFFFFFFFFFFFF
ItemBrokerTypeAdornment = 134217728
ItemBrokerTypeAmmo = 1024
ItemBrokerTypeAttuneable = 16384
ItemBrokerTypeBag = 2048
ItemBrokerTypeBauble = 16777216
ItemBrokerTypeBook = 128
ItemBrokerTypeChainarmor = 2097152
ItemBrokerTypeCloak = 1073741824
ItemBrokerTypeClotharmor = 524288
ItemBrokerTypeCollectable = 67108864
ItemBrokerTypeCrushweapon = 4
ItemBrokerTypeDrink = 131072
ItemBrokerTypeFood = 4096
ItemBrokerTypeHouseitem = 512
ItemBrokerTypeJewelry = 262144
ItemBrokerTypeLeatherarmor = 1048576
ItemBrokerTypeLore = 8192
ItemBrokerTypeMisc = 1
ItemBrokerTypePierceweapon = 8
ItemBrokerTypePlatearmor = 4194304
ItemBrokerTypePoison = 65536
ItemBrokerTypePotion = 32768
ItemBrokerTypeRecipebook = 8388608
ItemBrokerTypeSalesdisplay = 33554432
ItemBrokerTypeShield = 32
ItemBrokerTypeSlashweapon = 2
ItemBrokerTypeSpellscroll = 64
ItemBrokerTypeTinkered = 268435456
ItemBrokerTypeTradeskill = 256
)
// 2-handed weapon broker types
const (
ItemBrokerType2HCrush = 17179869184
ItemBrokerType2HPierce = 34359738368
ItemBrokerType2HSlash = 8589934592
)
// Broker slot flags
const (
ItemBrokerSlotAny = 0xFFFFFFFF
ItemBrokerSlotAmmo = 65536
ItemBrokerSlotCharm = 524288
ItemBrokerSlotChest = 32
ItemBrokerSlotCloak = 262144
ItemBrokerSlotDrink = 2097152
ItemBrokerSlotEars = 4096
ItemBrokerSlotFeet = 1024
ItemBrokerSlotFood = 1048576
ItemBrokerSlotForearms = 128
ItemBrokerSlotHands = 256
ItemBrokerSlotHead = 16
ItemBrokerSlotLegs = 512
ItemBrokerSlotNeck = 8192
ItemBrokerSlotPrimary = 1
ItemBrokerSlotPrimary2H = 2
ItemBrokerSlotRangeWeapon = 32768
ItemBrokerSlotRing = 2048
ItemBrokerSlotSecondary = 8
ItemBrokerSlotShoulders = 64
ItemBrokerSlotWaist = 131072
ItemBrokerSlotWrist = 16384
)
// Broker stat type flags
const (
ItemBrokerStatTypeNone = 0
ItemBrokerStatTypeDef = 2
ItemBrokerStatTypeStr = 4
ItemBrokerStatTypeSta = 8
ItemBrokerStatTypeAgi = 16
ItemBrokerStatTypeWis = 32
ItemBrokerStatTypeInt = 64
ItemBrokerStatTypeHealth = 128
ItemBrokerStatTypePower = 256
ItemBrokerStatTypeHeat = 512
ItemBrokerStatTypeCold = 1024
ItemBrokerStatTypeMagic = 2048
ItemBrokerStatTypeMental = 4096
ItemBrokerStatTypeDivine = 8192
ItemBrokerStatTypePoison = 16384
ItemBrokerStatTypeDisease = 32768
ItemBrokerStatTypeCrush = 65536
ItemBrokerStatTypeSlash = 131072
ItemBrokerStatTypePierce = 262144
ItemBrokerStatTypeCritical = 524288
ItemBrokerStatTypeDblAttack = 1048576
ItemBrokerStatTypeAbilityMod = 2097152
ItemBrokerStatTypePotency = 4194304
ItemBrokerStatTypeAEAutoattack = 8388608
ItemBrokerStatTypeAttackspeed = 16777216
ItemBrokerStatTypeBlockchance = 33554432
ItemBrokerStatTypeCastingspeed = 67108864
ItemBrokerStatTypeCritbonus = 134217728
ItemBrokerStatTypeCritchance = 268435456
ItemBrokerStatTypeDPS = 536870912
ItemBrokerStatTypeFlurrychance = 1073741824
ItemBrokerStatTypeHategain = 2147483648
ItemBrokerStatTypeMitigation = 4294967296
ItemBrokerStatTypeMultiAttack = 8589934592
ItemBrokerStatTypeRecovery = 17179869184
ItemBrokerStatTypeReuseSpeed = 34359738368
ItemBrokerStatTypeSpellWpndmg = 68719476736
ItemBrokerStatTypeStrikethrough = 137438953472
ItemBrokerStatTypeToughness = 274877906944
ItemBrokerStatTypeWeapondmg = 549755813888
)
// Special slot values
const (
OverflowSlot = 0xFFFFFFFE
SlotInvalid = 0xFFFF
)
// Basic item stats (0-4)
const (
ItemStatStr = 0
ItemStatSta = 1
ItemStatAgi = 2
ItemStatWis = 3
ItemStatInt = 4
)
// Skill-based stats (100+)
const (
ItemStatAdorning = 100
ItemStatAggression = 101
ItemStatArtificing = 102
ItemStatArtistry = 103
ItemStatChemistry = 104
ItemStatCrushing = 105
ItemStatDefense = 106
ItemStatDeflection = 107
ItemStatDisruption = 108
ItemStatFishing = 109
ItemStatFletching = 110
ItemStatFocus = 111
ItemStatForesting = 112
ItemStatGathering = 113
ItemStatMetalShaping = 114
ItemStatMetalworking = 115
ItemStatMining = 116
ItemStatMinistration = 117
ItemStatOrdination = 118
ItemStatParry = 119
ItemStatPiercing = 120
ItemStatRanged = 121
ItemStatSafeFall = 122
ItemStatScribing = 123
ItemStatSculpting = 124
ItemStatSlashing = 125
ItemStatSubjugation = 126
ItemStatSwimming = 127
ItemStatTailoring = 128
ItemStatTinkering = 129
ItemStatTransmuting = 130
ItemStatTrapping = 131
ItemStatWeaponSkills = 132
ItemStatPowerCostReduction = 133
ItemStatSpellAvoidance = 134
)
// Resistance stats (200+)
const (
ItemStatVsPhysical = 200
ItemStatVsHeat = 201 // elemental
ItemStatVsPoison = 202 // noxious
ItemStatVsMagic = 203 // arcane
ItemStatVsSlash = 204
ItemStatVsCrush = 205
ItemStatVsPierce = 206
ItemStatVsCold = 207
ItemStatVsMental = 208
ItemStatVsDivine = 209
ItemStatVsDrowning = 210
ItemStatVsFalling = 211
ItemStatVsPain = 212
ItemStatVsMelee = 213
ItemStatVsDisease = 214
)
// Damage type stats (300+)
const (
ItemStatDmgSlash = 300
ItemStatDmgCrush = 301
ItemStatDmgPierce = 302
ItemStatDmgHeat = 303
ItemStatDmgCold = 304
ItemStatDmgMagic = 305
ItemStatDmgMental = 306
ItemStatDmgDivine = 307
ItemStatDmgDisease = 308
ItemStatDmgPoison = 309
ItemStatDmgDrowning = 310
ItemStatDmgFalling = 311
ItemStatDmgPain = 312
ItemStatDmgMelee = 313
)
// Pool stats (500+)
const (
ItemStatHealth = 500
ItemStatPower = 501
ItemStatConcentration = 502
ItemStatSavagery = 503
)
// Advanced stats (600+)
const (
ItemStatHPRegen = 600
ItemStatManaRegen = 601
ItemStatHPRegenPPT = 602
ItemStatMPRegenPPT = 603
ItemStatCombatHPRegenPPT = 604
ItemStatCombatMPRegenPPT = 605
ItemStatMaxHP = 606
ItemStatMaxHPPerc = 607
ItemStatMaxHPPercFinal = 608
ItemStatSpeed = 609
ItemStatSlow = 610
ItemStatMountSpeed = 611
ItemStatMountAirSpeed = 612
ItemStatLeapSpeed = 613
ItemStatLeapTime = 614
ItemStatGlideEfficiency = 615
ItemStatOffensiveSpeed = 616
ItemStatAttackSpeed = 617
ItemStatSpellWeaponAttackSpeed = 618
ItemStatMaxMana = 619
ItemStatMaxManaPerc = 620
ItemStatMaxAttPerc = 621
ItemStatBlurVision = 622
ItemStatMagicLevelImmunity = 623
ItemStatHateGainMod = 624
ItemStatCombatExpMod = 625
ItemStatTradeskillExpMod = 626
ItemStatAchievementExpMod = 627
ItemStatSizeMod = 628
ItemStatDPS = 629
ItemStatSpellWeaponDPS = 630
ItemStatStealth = 631
ItemStatInvis = 632
ItemStatSeeStealth = 633
ItemStatSeeInvis = 634
ItemStatEffectiveLevelMod = 635
ItemStatRiposteChance = 636
ItemStatParryChance = 637
ItemStatDodgeChance = 638
ItemStatAEAutoattackChance = 639
ItemStatSpellWeaponAEAutoattackChance = 640
ItemStatMultiattackChance = 641
ItemStatPvPDoubleAttackChance = 642
ItemStatSpellWeaponDoubleAttackChance = 643
ItemStatPvPSpellWeaponDoubleAttackChance = 644
ItemStatSpellMultiAttackChance = 645
ItemStatPvPSpellDoubleAttackChance = 646
ItemStatFlurry = 647
ItemStatSpellWeaponFlurry = 648
ItemStatMeleeDamageMultiplier = 649
ItemStatExtraHarvestChance = 650
ItemStatExtraShieldBlockChance = 651
ItemStatItemHPRegenPPT = 652
ItemStatItemPPRegenPPT = 653
ItemStatMeleeCritChance = 654
ItemStatCritAvoidance = 655
ItemStatBeneficialCritChance = 656
ItemStatCritBonus = 657
ItemStatPvPCritBonus = 658
ItemStatPotency = 659
ItemStatPvPPotency = 660
ItemStatUnconsciousHPMod = 661
ItemStatAbilityReuseSpeed = 662
ItemStatAbilityRecoverySpeed = 663
ItemStatAbilityCastingSpeed = 664
ItemStatSpellReuseSpeed = 665
ItemStatMeleeWeaponRange = 666
ItemStatRangedWeaponRange = 667
ItemStatFallingDamageReduction = 668
ItemStatRiposteDamage = 669
ItemStatMinimumDeflectionChance = 670
ItemStatMovementWeave = 671
ItemStatCombatHPRegen = 672
ItemStatCombatManaRegen = 673
ItemStatContestSpeedBoost = 674
ItemStatTrackingAvoidance = 675
ItemStatStealthInvisSpeedMod = 676
ItemStatLootCoin = 677
ItemStatArmorMitigationIncrease = 678
ItemStatAmmoConservation = 679
ItemStatStrikethrough = 680
ItemStatStatusBonus = 681
ItemStatAccuracy = 682
ItemStatCounterstrike = 683
ItemStatShieldBash = 684
ItemStatWeaponDamageBonus = 685
ItemStatWeaponDamageBonusMeleeOnly = 686
ItemStatAdditionalRiposteChance = 687
ItemStatCriticalMitigation = 688
ItemStatPvPToughness = 689
ItemStatPvPLethality = 690
ItemStatStaminaBonus = 691
ItemStatWisdomMitBonus = 692
ItemStatHealReceive = 693
ItemStatHealReceivePerc = 694
ItemStatPvPCriticalMitigation = 695
ItemStatBaseAvoidanceBonus = 696
ItemStatInCombatSavageryRegen = 697
ItemStatOutOfCombatSavageryRegen = 698
ItemStatSavageryRegen = 699
ItemStatSavageryGainMod = 6100
ItemStatMaxSavageryLevel = 6101
ItemStatSpellWeaponDamageBonus = 6102
ItemStatInCombatDissonanceRegen = 6103
ItemStatOutOfCombatDissonanceRegen = 6104
ItemStatDissonanceRegen = 6105
ItemStatDissonanceGainMod = 6106
ItemStatAEAutoattackAvoid = 6107
ItemStatAgnosticDamageBonus = 6108
ItemStatAgnosticHealBonus = 6109
ItemStatTitheGain = 6110
ItemStatFerver = 6111
ItemStatResolve = 6112
ItemStatCombatMitigation = 6113
ItemStatAbilityMitigation = 6114
ItemStatMultiAttackAvoidance = 6115
ItemStatDoubleCastAvoidance = 6116
ItemStatAbilityDoubleCastAvoidance = 6117
ItemStatDamagePerSecondMitigation = 6118
ItemStatFerverMitigation = 6119
ItemStatFlurryAvoidance = 6120
ItemStatWeaponDamageBonusMitigation = 6121
ItemStatAbilityDoubleCastChance = 6122
ItemStatAbilityModifierMitigation = 6123
ItemStatStatusEarned = 6124
)
// Spell/ability modifier stats (700+)
const (
ItemStatSpellDamage = 700
ItemStatHealAmount = 701
ItemStatSpellAndHeal = 702
ItemStatCombatArtDamage = 703
ItemStatSpellAndCombatArtDamage = 704
ItemStatTauntAmount = 705
ItemStatTauntAndCombatArtDamage = 706
ItemStatAbilityModifier = 707
)
// Server-only stats (800+) - never sent to client
const (
ItemStatDurabilityMod = 800
ItemStatDurabilityAdd = 801
ItemStatProgressAdd = 802
ItemStatProgressMod = 803
ItemStatSuccessMod = 804
ItemStatCritSuccessMod = 805
ItemStatExDurabilityMod = 806
ItemStatExDurabilityAdd = 807
ItemStatExProgressMod = 808
ItemStatExProgressAdd = 809
ItemStatExSuccessMod = 810
ItemStatExCritSuccessMod = 811
ItemStatExCritFailureMod = 812
ItemStatRareHarvestChance = 813
ItemStatMaxCrafting = 814
ItemStatComponentRefund = 815
ItemStatBountifulHarvest = 816
)
// Uncontested stats (850+)
const (
ItemStatUncontestedParry = 850
ItemStatUncontestedBlock = 851
ItemStatUncontestedDodge = 852
ItemStatUncontestedRiposte = 853
)
// Display flags
const (
DisplayFlagRedText = 1
DisplayFlagNoGuildStatus = 8
DisplayFlagNoBuyback = 16
DisplayFlagNotForSale = 64
DisplayFlagNoBuy = 128
)
// House store item flags
const (
HouseStoreItemTextRed = 1
HouseStoreUnknownBit2 = 2
HouseStoreUnknownBit4 = 4
HouseStoreForSale = 8
HouseStoreUnknownBit16 = 16
HouseStoreVaultTab = 32
)
// Log category
const (
LogCategoryItems = "Items"
)
// Item validation constants
const (
MaxItemNameLength = 255 // Maximum length for item names
MaxItemDescLength = 1000 // Maximum length for item descriptions
)
// Default item values
const (
DefaultItemCondition = 100 // 100% condition for new items
DefaultItemDurability = 100 // 100% durability for new items
)

View File

@ -0,0 +1,555 @@
package items
import (
"fmt"
"log"
)
// NewEquipmentItemList creates a new equipment item list
func NewEquipmentItemList() *EquipmentItemList {
return &EquipmentItemList{
items: [NumSlots]*Item{},
appearanceType: BaseEquipment,
}
}
// NewEquipmentItemListFromCopy creates a copy of an equipment list
func NewEquipmentItemListFromCopy(source *EquipmentItemList) *EquipmentItemList {
if source == nil {
return NewEquipmentItemList()
}
source.mutex.RLock()
defer source.mutex.RUnlock()
equipment := &EquipmentItemList{
appearanceType: source.appearanceType,
}
// Copy all equipped items
for i, item := range source.items {
if item != nil {
equipment.items[i] = item.Copy()
}
}
return equipment
}
// GetAllEquippedItems returns all equipped items
func (eil *EquipmentItemList) GetAllEquippedItems() []*Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
var equippedItems []*Item
for _, item := range eil.items {
if item != nil {
equippedItems = append(equippedItems, item)
}
}
return equippedItems
}
// ResetPackets resets packet data
func (eil *EquipmentItemList) ResetPackets() {
eil.mutex.Lock()
defer eil.mutex.Unlock()
eil.xorPacket = nil
eil.origPacket = nil
}
// HasItem checks if a specific item ID is equipped
func (eil *EquipmentItemList) HasItem(itemID int32) bool {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
for _, item := range eil.items {
if item != nil && item.Details.ItemID == itemID {
return true
}
}
return false
}
// GetNumberOfItems returns the number of equipped items
func (eil *EquipmentItemList) GetNumberOfItems() int8 {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
count := int8(0)
for _, item := range eil.items {
if item != nil {
count++
}
}
return count
}
// GetWeight returns the total weight of equipped items
func (eil *EquipmentItemList) GetWeight() int32 {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
totalWeight := int32(0)
for _, item := range eil.items {
if item != nil {
totalWeight += item.GenericInfo.Weight * int32(item.Details.Count)
}
}
return totalWeight
}
// GetItemFromUniqueID gets an equipped item by unique ID
func (eil *EquipmentItemList) GetItemFromUniqueID(uniqueID int32) *Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
for _, item := range eil.items {
if item != nil && int32(item.Details.UniqueID) == uniqueID {
return item
}
}
return nil
}
// GetItemFromItemID gets an equipped item by item template ID
func (eil *EquipmentItemList) GetItemFromItemID(itemID int32) *Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
for _, item := range eil.items {
if item != nil && item.Details.ItemID == itemID {
return item
}
}
return nil
}
// SetItem sets an item in a specific equipment slot
func (eil *EquipmentItemList) SetItem(slotID int8, item *Item, locked bool) {
if slotID < 0 || slotID >= NumSlots {
return
}
if !locked {
eil.mutex.Lock()
defer eil.mutex.Unlock()
}
eil.items[slotID] = item
if item != nil {
item.Details.SlotID = int16(slotID)
item.Details.AppearanceType = int16(eil.appearanceType)
}
}
// RemoveItem removes an item from a specific slot
func (eil *EquipmentItemList) RemoveItem(slot int8, deleteItem bool) {
if slot < 0 || slot >= NumSlots {
return
}
eil.mutex.Lock()
defer eil.mutex.Unlock()
item := eil.items[slot]
eil.items[slot] = nil
if deleteItem && item != nil {
item.NeedsDeletion = true
}
}
// GetItem gets an item from a specific slot
func (eil *EquipmentItemList) GetItem(slotID int8) *Item {
if slotID < 0 || slotID >= NumSlots {
return nil
}
eil.mutex.RLock()
defer eil.mutex.RUnlock()
return eil.items[slotID]
}
// AddItem adds an item to the equipment (finds appropriate slot)
func (eil *EquipmentItemList) AddItem(slot int8, item *Item) bool {
if item == nil {
return false
}
// Check if the specific slot is requested and valid
if slot >= 0 && slot < NumSlots {
eil.mutex.Lock()
defer eil.mutex.Unlock()
if eil.items[slot] == nil {
eil.items[slot] = item
item.Details.SlotID = int16(slot)
item.Details.AppearanceType = int16(eil.appearanceType)
return true
}
}
// Find a free slot that the item can be equipped in
freeSlot := eil.GetFreeSlot(item, slot, 0)
if freeSlot < NumSlots {
eil.SetItem(freeSlot, item, false)
return true
}
return false
}
// CheckEquipSlot checks if an item can be equipped in a specific slot
func (eil *EquipmentItemList) CheckEquipSlot(item *Item, slot int8) bool {
if item == nil || slot < 0 || slot >= NumSlots {
return false
}
// Check if item has the required slot data
return item.HasSlot(slot, -1)
}
// CanItemBeEquippedInSlot checks if an item can be equipped in a slot
func (eil *EquipmentItemList) CanItemBeEquippedInSlot(item *Item, slot int8) bool {
if item == nil || slot < 0 || slot >= NumSlots {
return false
}
// Check slot compatibility
if !eil.CheckEquipSlot(item, slot) {
return false
}
// Check if slot is already occupied
eil.mutex.RLock()
defer eil.mutex.RUnlock()
return eil.items[slot] == nil
}
// GetFreeSlot finds a free slot for an item
func (eil *EquipmentItemList) GetFreeSlot(item *Item, preferredSlot int8, version int16) int8 {
if item == nil {
return NumSlots // Invalid slot
}
eil.mutex.RLock()
defer eil.mutex.RUnlock()
// If preferred slot is specified and available, use it
if preferredSlot >= 0 && preferredSlot < NumSlots {
if eil.items[preferredSlot] == nil && item.HasSlot(preferredSlot, -1) {
return preferredSlot
}
}
// Search through all possible slots for this item
for slot := int8(0); slot < NumSlots; slot++ {
if eil.items[slot] == nil && item.HasSlot(slot, -1) {
return slot
}
}
return NumSlots // No free slot found
}
// CheckSlotConflict checks for slot conflicts (lore items, etc.)
func (eil *EquipmentItemList) CheckSlotConflict(item *Item, checkLoreOnly bool, loreStackCount *int16) int32 {
if item == nil {
return 0
}
eil.mutex.RLock()
defer eil.mutex.RUnlock()
// Check for lore conflicts
if item.CheckFlag(Lore) || item.CheckFlag(LoreEquip) {
stackCount := int16(0)
for _, equippedItem := range eil.items {
if equippedItem != nil && equippedItem.Details.ItemID == item.Details.ItemID {
stackCount++
}
}
if loreStackCount != nil {
*loreStackCount = stackCount
}
if stackCount > 0 {
return 1 // Lore conflict
}
}
return 0 // No conflict
}
// GetSlotByItem finds the slot an item is equipped in
func (eil *EquipmentItemList) GetSlotByItem(item *Item) int8 {
if item == nil {
return NumSlots
}
eil.mutex.RLock()
defer eil.mutex.RUnlock()
for slot, equippedItem := range eil.items {
if equippedItem == item {
return int8(slot)
}
}
return NumSlots // Not found
}
// CalculateEquipmentBonuses calculates stat bonuses from all equipped items
func (eil *EquipmentItemList) CalculateEquipmentBonuses() *ItemStatsValues {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
totalBonuses := &ItemStatsValues{}
for _, item := range eil.items {
if item != nil {
// TODO: Implement item bonus calculation
// This should be handled by the master item list
itemBonuses := &ItemStatsValues{} // placeholder
if itemBonuses != nil {
// Add item bonuses to total
totalBonuses.Str += itemBonuses.Str
totalBonuses.Sta += itemBonuses.Sta
totalBonuses.Agi += itemBonuses.Agi
totalBonuses.Wis += itemBonuses.Wis
totalBonuses.Int += itemBonuses.Int
totalBonuses.VsSlash += itemBonuses.VsSlash
totalBonuses.VsCrush += itemBonuses.VsCrush
totalBonuses.VsPierce += itemBonuses.VsPierce
totalBonuses.VsPhysical += itemBonuses.VsPhysical
totalBonuses.VsHeat += itemBonuses.VsHeat
totalBonuses.VsCold += itemBonuses.VsCold
totalBonuses.VsMagic += itemBonuses.VsMagic
totalBonuses.VsMental += itemBonuses.VsMental
totalBonuses.VsDivine += itemBonuses.VsDivine
totalBonuses.VsDisease += itemBonuses.VsDisease
totalBonuses.VsPoison += itemBonuses.VsPoison
totalBonuses.Health += itemBonuses.Health
totalBonuses.Power += itemBonuses.Power
totalBonuses.Concentration += itemBonuses.Concentration
totalBonuses.AbilityModifier += itemBonuses.AbilityModifier
totalBonuses.CriticalMitigation += itemBonuses.CriticalMitigation
totalBonuses.ExtraShieldBlockChance += itemBonuses.ExtraShieldBlockChance
totalBonuses.BeneficialCritChance += itemBonuses.BeneficialCritChance
totalBonuses.CritBonus += itemBonuses.CritBonus
totalBonuses.Potency += itemBonuses.Potency
totalBonuses.HateGainMod += itemBonuses.HateGainMod
totalBonuses.AbilityReuseSpeed += itemBonuses.AbilityReuseSpeed
totalBonuses.AbilityCastingSpeed += itemBonuses.AbilityCastingSpeed
totalBonuses.AbilityRecoverySpeed += itemBonuses.AbilityRecoverySpeed
totalBonuses.SpellReuseSpeed += itemBonuses.SpellReuseSpeed
totalBonuses.SpellMultiAttackChance += itemBonuses.SpellMultiAttackChance
totalBonuses.DPS += itemBonuses.DPS
totalBonuses.AttackSpeed += itemBonuses.AttackSpeed
totalBonuses.MultiAttackChance += itemBonuses.MultiAttackChance
totalBonuses.Flurry += itemBonuses.Flurry
totalBonuses.AEAutoattackChance += itemBonuses.AEAutoattackChance
totalBonuses.Strikethrough += itemBonuses.Strikethrough
totalBonuses.Accuracy += itemBonuses.Accuracy
totalBonuses.OffensiveSpeed += itemBonuses.OffensiveSpeed
totalBonuses.UncontestedParry += itemBonuses.UncontestedParry
totalBonuses.UncontestedBlock += itemBonuses.UncontestedBlock
totalBonuses.UncontestedDodge += itemBonuses.UncontestedDodge
totalBonuses.UncontestedRiposte += itemBonuses.UncontestedRiposte
totalBonuses.SizeMod += itemBonuses.SizeMod
}
}
}
return totalBonuses
}
// SetAppearanceType sets the appearance type (normal or appearance equipment)
func (eil *EquipmentItemList) SetAppearanceType(appearanceType int8) {
eil.mutex.Lock()
defer eil.mutex.Unlock()
eil.appearanceType = appearanceType
// Update all equipped items with new appearance type
for _, item := range eil.items {
if item != nil {
item.Details.AppearanceType = int16(appearanceType)
}
}
}
// GetAppearanceType gets the current appearance type
func (eil *EquipmentItemList) GetAppearanceType() int8 {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
return eil.appearanceType
}
// ValidateEquipment validates all equipped items
func (eil *EquipmentItemList) ValidateEquipment() *ItemValidationResult {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
result := &ItemValidationResult{Valid: true}
for slot, item := range eil.items {
if item != nil {
// Validate item
itemResult := item.Validate()
if !itemResult.Valid {
result.Valid = false
for _, err := range itemResult.Errors {
result.Errors = append(result.Errors, fmt.Sprintf("Slot %d: %s", slot, err))
}
}
// Check slot compatibility
if !item.HasSlot(int8(slot), -1) {
result.Valid = false
result.Errors = append(result.Errors, fmt.Sprintf("Item %s cannot be equipped in slot %d", item.Name, slot))
}
}
}
return result
}
// GetEquippedItemsByType returns equipped items of a specific type
func (eil *EquipmentItemList) GetEquippedItemsByType(itemType int8) []*Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
var matchingItems []*Item
for _, item := range eil.items {
if item != nil && item.GenericInfo.ItemType == itemType {
matchingItems = append(matchingItems, item)
}
}
return matchingItems
}
// GetWeapons returns all equipped weapons
func (eil *EquipmentItemList) GetWeapons() []*Item {
return eil.GetEquippedItemsByType(ItemTypeWeapon)
}
// GetArmor returns all equipped armor pieces
func (eil *EquipmentItemList) GetArmor() []*Item {
return eil.GetEquippedItemsByType(ItemTypeArmor)
}
// GetJewelry returns all equipped jewelry
func (eil *EquipmentItemList) GetJewelry() []*Item {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
var jewelry []*Item
// Check ring slots
if eil.items[EQ2LRingSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2LRingSlot])
}
if eil.items[EQ2RRingSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2RRingSlot])
}
// Check ear slots
if eil.items[EQ2EarsSlot1] != nil {
jewelry = append(jewelry, eil.items[EQ2EarsSlot1])
}
if eil.items[EQ2EarsSlot2] != nil {
jewelry = append(jewelry, eil.items[EQ2EarsSlot2])
}
// Check neck slot
if eil.items[EQ2NeckSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2NeckSlot])
}
// Check wrist slots
if eil.items[EQ2LWristSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2LWristSlot])
}
if eil.items[EQ2RWristSlot] != nil {
jewelry = append(jewelry, eil.items[EQ2RWristSlot])
}
return jewelry
}
// HasWeaponEquipped checks if any weapon is equipped
func (eil *EquipmentItemList) HasWeaponEquipped() bool {
weapons := eil.GetWeapons()
return len(weapons) > 0
}
// HasShieldEquipped checks if a shield is equipped
func (eil *EquipmentItemList) HasShieldEquipped() bool {
item := eil.GetItem(EQ2SecondarySlot)
return item != nil && item.IsShield()
}
// HasTwoHandedWeapon checks if a two-handed weapon is equipped
func (eil *EquipmentItemList) HasTwoHandedWeapon() bool {
primaryItem := eil.GetItem(EQ2PrimarySlot)
if primaryItem != nil && primaryItem.IsWeapon() && primaryItem.WeaponInfo != nil {
return primaryItem.WeaponInfo.WieldType == ItemWieldTypeTwoHand
}
return false
}
// CanDualWield checks if dual wielding is possible with current equipment
func (eil *EquipmentItemList) CanDualWield() bool {
primaryItem := eil.GetItem(EQ2PrimarySlot)
secondaryItem := eil.GetItem(EQ2SecondarySlot)
if primaryItem != nil && secondaryItem != nil {
// Both items must be weapons that can be dual wielded
if primaryItem.IsWeapon() && secondaryItem.IsWeapon() {
if primaryItem.WeaponInfo != nil && secondaryItem.WeaponInfo != nil {
return primaryItem.WeaponInfo.WieldType == ItemWieldTypeDual &&
secondaryItem.WeaponInfo.WieldType == ItemWieldTypeDual
}
}
}
return false
}
// String returns a string representation of the equipment list
func (eil *EquipmentItemList) String() string {
eil.mutex.RLock()
defer eil.mutex.RUnlock()
equippedCount := 0
for _, item := range eil.items {
if item != nil {
equippedCount++
}
}
return fmt.Sprintf("EquipmentItemList{Equipped: %d/%d, AppearanceType: %d}",
equippedCount, NumSlots, eil.appearanceType)
}
func init() {
log.Printf("Equipment item list system initialized")
}

View File

@ -0,0 +1,727 @@
package items
import (
"fmt"
"log"
"sync"
"time"
)
// SpellManager defines the interface for spell-related operations needed by items
type SpellManager interface {
// GetSpell retrieves spell information by ID and tier
GetSpell(spellID uint32, tier int8) (Spell, error)
// GetSpellsBySkill gets spells associated with a skill
GetSpellsBySkill(skillID uint32) ([]uint32, error)
// ValidateSpellID checks if a spell ID is valid
ValidateSpellID(spellID uint32) bool
}
// PlayerManager defines the interface for player-related operations needed by items
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
// GetPlayerName gets a player's name
GetPlayerName(playerID uint32) (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)
// SerializeItem serializes an item for network transmission
SerializeItem(item *Item, clientVersion int16, player Player) ([]byte, 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
// GetString retrieves a string rule value
GetString(category, rule string) string
}
// DatabaseService defines the interface for item persistence operations
type DatabaseService interface {
// LoadItems loads all item templates from the database
LoadItems(masterList *MasterItemList) error
// SaveItem saves an item template to the database
SaveItem(item *Item) error
// DeleteItem removes an item template from the database
DeleteItem(itemID int32) error
// LoadPlayerItems loads a player's inventory from the database
LoadPlayerItems(playerID uint32) (*PlayerItemList, error)
// SavePlayerItems saves a player's inventory to the database
SavePlayerItems(playerID uint32, itemList *PlayerItemList) error
// LoadPlayerEquipment loads a player's equipment from the database
LoadPlayerEquipment(playerID uint32, appearanceType int8) (*EquipmentItemList, error)
// SavePlayerEquipment saves a player's equipment to the database
SavePlayerEquipment(playerID uint32, equipment *EquipmentItemList) error
// LoadItemStats loads item stat mappings from the database
LoadItemStats() (map[string]int32, map[int32]string, error)
// SaveItemStat saves an item stat mapping to the database
SaveItemStat(statID int32, statName string) error
}
// QuestManager defines the interface for quest-related item operations
type QuestManager interface {
// CheckQuestPrerequisites checks if a player meets quest prerequisites for an item
CheckQuestPrerequisites(playerID uint32, questID int32) bool
// GetQuestRewards gets quest rewards for an item
GetQuestRewards(questID int32) ([]*QuestRewardData, error)
// IsQuestItem checks if an item is a quest item
IsQuestItem(itemID int32) bool
}
// BrokerManager defines the interface for broker/marketplace operations
type BrokerManager interface {
// SearchItems searches for items on the broker
SearchItems(criteria *ItemSearchCriteria) ([]*Item, error)
// ListItem lists an item on the broker
ListItem(playerID uint32, item *Item, price int64) error
// BuyItem purchases an item from the broker
BuyItem(playerID uint32, itemID int32, sellerID uint32) error
// GetItemPrice gets the current market price for an item
GetItemPrice(itemID int32) (int64, error)
}
// CraftingManager defines the interface for crafting-related item operations
type CraftingManager interface {
// CanCraftItem checks if a player can craft an item
CanCraftItem(playerID uint32, itemID int32) bool
// GetCraftingRequirements gets crafting requirements for an item
GetCraftingRequirements(itemID int32) ([]CraftingRequirement, error)
// CraftItem handles item crafting
CraftItem(playerID uint32, itemID int32, quality int8) (*Item, error)
}
// HousingManager defines the interface for housing-related item operations
type HousingManager interface {
// CanPlaceItem checks if an item can be placed in a house
CanPlaceItem(playerID uint32, houseID int32, item *Item) bool
// PlaceItem places an item in a house
PlaceItem(playerID uint32, houseID int32, item *Item, location HouseLocation) error
// RemoveItem removes an item from a house
RemoveItem(playerID uint32, houseID int32, itemID int32) error
// GetHouseItems gets all items in a house
GetHouseItems(houseID int32) ([]*Item, error)
}
// LootManager defines the interface for loot-related operations
type LootManager interface {
// GenerateLoot generates loot for a loot table
GenerateLoot(lootTableID int32, playerLevel int16) ([]*Item, error)
// DistributeLoot distributes loot to players
DistributeLoot(items []*Item, playerIDs []uint32, lootMethod int8) error
// CanLootItem checks if a player can loot an item
CanLootItem(playerID uint32, item *Item) bool
}
// 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
GetDescription() string
}
// Player represents a player in the game
type Player interface {
GetID() uint32
GetName() string
GetLevel() int16
GetAdventureClass() int8
GetTradeskillClass() int8
GetRace() int8
GetGender() int8
GetAlignment() int8
}
// CraftingRequirement represents a crafting requirement
type CraftingRequirement struct {
ItemID int32 `json:"item_id"`
Quantity int16 `json:"quantity"`
Skill int32 `json:"skill"`
Level int16 `json:"level"`
}
// HouseLocation represents a location within a house
type HouseLocation struct {
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
Heading float32 `json:"heading"`
Pitch float32 `json:"pitch"`
Roll float32 `json:"roll"`
Location int8 `json:"location"` // 0=floor, 1=ceiling, 2=wall
}
// ItemSystemAdapter provides a high-level interface to the complete item system
type ItemSystemAdapter struct {
masterList *MasterItemList
playerLists map[uint32]*PlayerItemList
equipmentLists map[uint32]*EquipmentItemList
spellManager SpellManager
playerManager PlayerManager
packetManager PacketManager
ruleManager RuleManager
databaseService DatabaseService
questManager QuestManager
brokerManager BrokerManager
craftingManager CraftingManager
housingManager HousingManager
lootManager LootManager
mutex sync.RWMutex
}
// NewItemSystemAdapter creates a new item system adapter with all dependencies
func NewItemSystemAdapter(
masterList *MasterItemList,
spellManager SpellManager,
playerManager PlayerManager,
packetManager PacketManager,
ruleManager RuleManager,
databaseService DatabaseService,
questManager QuestManager,
brokerManager BrokerManager,
craftingManager CraftingManager,
housingManager HousingManager,
lootManager LootManager,
) *ItemSystemAdapter {
return &ItemSystemAdapter{
masterList: masterList,
playerLists: make(map[uint32]*PlayerItemList),
equipmentLists: make(map[uint32]*EquipmentItemList),
spellManager: spellManager,
playerManager: playerManager,
packetManager: packetManager,
ruleManager: ruleManager,
databaseService: databaseService,
questManager: questManager,
brokerManager: brokerManager,
craftingManager: craftingManager,
housingManager: housingManager,
lootManager: lootManager,
}
}
// Initialize sets up the item system (loads items from database, etc.)
func (isa *ItemSystemAdapter) Initialize() error {
// Load items from database
err := isa.databaseService.LoadItems(isa.masterList)
if err != nil {
return err
}
// Load item stat mappings
statsStrings, statsIDs, err := isa.databaseService.LoadItemStats()
if err != nil {
return err
}
isa.masterList.mutex.Lock()
isa.masterList.mappedItemStatsStrings = statsStrings
isa.masterList.mappedItemStatTypeIDs = statsIDs
isa.masterList.mutex.Unlock()
return nil
}
// GetPlayerInventory gets or loads a player's inventory
func (isa *ItemSystemAdapter) GetPlayerInventory(playerID uint32) (*PlayerItemList, error) {
isa.mutex.Lock()
defer isa.mutex.Unlock()
if itemList, exists := isa.playerLists[playerID]; exists {
return itemList, nil
}
// Load from database
itemList, err := isa.databaseService.LoadPlayerItems(playerID)
if err != nil {
return nil, err
}
if itemList == nil {
itemList = NewPlayerItemList()
}
isa.playerLists[playerID] = itemList
return itemList, nil
}
// GetPlayerEquipment gets or loads a player's equipment
func (isa *ItemSystemAdapter) GetPlayerEquipment(playerID uint32, appearanceType int8) (*EquipmentItemList, error) {
isa.mutex.Lock()
defer isa.mutex.Unlock()
key := uint32(playerID)*10 + uint32(appearanceType)
if equipment, exists := isa.equipmentLists[key]; exists {
return equipment, nil
}
// Load from database
equipment, err := isa.databaseService.LoadPlayerEquipment(playerID, appearanceType)
if err != nil {
return nil, err
}
if equipment == nil {
equipment = NewEquipmentItemList()
equipment.SetAppearanceType(appearanceType)
}
isa.equipmentLists[key] = equipment
return equipment, nil
}
// SavePlayerData saves a player's item data
func (isa *ItemSystemAdapter) SavePlayerData(playerID uint32) error {
isa.mutex.RLock()
defer isa.mutex.RUnlock()
// Save inventory
if itemList, exists := isa.playerLists[playerID]; exists {
err := isa.databaseService.SavePlayerItems(playerID, itemList)
if err != nil {
return err
}
}
// Save equipment (both normal and appearance)
for key, equipment := range isa.equipmentLists {
if key/10 == playerID {
err := isa.databaseService.SavePlayerEquipment(playerID, equipment)
if err != nil {
return err
}
}
}
return nil
}
// GiveItemToPlayer gives an item to a player
func (isa *ItemSystemAdapter) GiveItemToPlayer(playerID uint32, itemID int32, quantity int16, addType AddItemType) error {
// Get item template
itemTemplate := isa.masterList.GetItem(itemID)
if itemTemplate == nil {
return ErrItemNotFound
}
// Create item instance
item := NewItemFromTemplate(itemTemplate)
item.Details.Count = quantity
// Get player inventory
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
// Try to add item to inventory
if !inventory.AddItem(item) {
return ErrInsufficientSpace
}
// Send update to player
player, err := isa.playerManager.GetPlayer(playerID)
if err != nil {
return err
}
clientVersion, _ := isa.packetManager.GetClientVersion(playerID)
packetData, err := isa.packetManager.SerializeItem(item, clientVersion, player)
if err != nil {
return err
}
return isa.packetManager.SendPacketToPlayer(playerID, packetData)
}
// RemoveItemFromPlayer removes an item from a player
func (isa *ItemSystemAdapter) RemoveItemFromPlayer(playerID uint32, uniqueID int32, quantity int16) error {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
item := inventory.GetItemFromUniqueID(uniqueID, true, true)
if item == nil {
return ErrItemNotFound
}
// Check if item can be removed
if item.IsItemLocked() {
return ErrItemLocked
}
if item.Details.Count <= quantity {
// Remove entire stack
inventory.RemoveItem(item, true, true)
} else {
// Reduce quantity
item.Details.Count -= quantity
}
return nil
}
// EquipItem equips an item for a player
func (isa *ItemSystemAdapter) EquipItem(playerID uint32, uniqueID int32, slot int8, appearanceType int8) error {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
equipment, err := isa.GetPlayerEquipment(playerID, appearanceType)
if err != nil {
return err
}
// Get item from inventory
item := inventory.GetItemFromUniqueID(uniqueID, false, true)
if item == nil {
return ErrItemNotFound
}
// Check if item can be equipped
if !equipment.CanItemBeEquippedInSlot(item, slot) {
return ErrCannotEquip
}
// Check class/race/level requirements
player, err := isa.playerManager.GetPlayer(playerID)
if err != nil {
return err
}
if !item.CheckClass(player.GetAdventureClass(), player.GetTradeskillClass()) {
return ErrCannotEquip
}
if !item.CheckClassLevel(player.GetAdventureClass(), player.GetTradeskillClass(), player.GetLevel()) {
return ErrCannotEquip
}
// Remove from inventory
inventory.RemoveItem(item, false, true)
// Check if slot is occupied and unequip current item
currentItem := equipment.GetItem(slot)
if currentItem != nil {
equipment.RemoveItem(slot, false)
inventory.AddItem(currentItem)
}
// Equip new item
equipment.SetItem(slot, item, false)
return nil
}
// UnequipItem unequips an item for a player
func (isa *ItemSystemAdapter) UnequipItem(playerID uint32, slot int8, appearanceType int8) error {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
equipment, err := isa.GetPlayerEquipment(playerID, appearanceType)
if err != nil {
return err
}
// Get equipped item
item := equipment.GetItem(slot)
if item == nil {
return ErrItemNotFound
}
// Check if item can be unequipped
if item.IsItemLocked() {
return ErrItemLocked
}
// Remove from equipment
equipment.RemoveItem(slot, false)
// Add to inventory
if !inventory.AddItem(item) {
// Inventory full, add to overflow
inventory.AddOverflowItem(item)
}
return nil
}
// MoveItem moves an item within a player's inventory
func (isa *ItemSystemAdapter) MoveItem(playerID uint32, fromBagID int32, fromSlot int16, toBagID int32, toSlot int16, appearanceType int8) error {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return err
}
// Get item from source location
item := inventory.GetItem(fromBagID, fromSlot, appearanceType)
if item == nil {
return ErrItemNotFound
}
// Check if item is locked
if item.IsItemLocked() {
return ErrItemLocked
}
// Move item
inventory.MoveItem(item, toBagID, toSlot, appearanceType, true)
return nil
}
// SearchBrokerItems searches for items on the broker
func (isa *ItemSystemAdapter) SearchBrokerItems(criteria *ItemSearchCriteria) ([]*Item, error) {
if isa.brokerManager == nil {
return nil, fmt.Errorf("broker manager not available")
}
return isa.brokerManager.SearchItems(criteria)
}
// CraftItem handles item crafting
func (isa *ItemSystemAdapter) CraftItem(playerID uint32, itemID int32, quality int8) (*Item, error) {
if isa.craftingManager == nil {
return nil, fmt.Errorf("crafting manager not available")
}
// Check if player can craft the item
if !isa.craftingManager.CanCraftItem(playerID, itemID) {
return nil, fmt.Errorf("player cannot craft this item")
}
// Craft the item
return isa.craftingManager.CraftItem(playerID, itemID, quality)
}
// GetPlayerItemStats returns statistics about a player's items
func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]interface{}, error) {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return nil, err
}
equipment, err := isa.GetPlayerEquipment(playerID, BaseEquipment)
if err != nil {
return nil, err
}
// Calculate equipment bonuses
bonuses := equipment.CalculateEquipmentBonuses()
return map[string]interface{}{
"player_id": playerID,
"total_items": inventory.GetNumberOfItems(),
"equipped_items": equipment.GetNumberOfItems(),
"inventory_weight": inventory.GetWeight(),
"equipment_weight": equipment.GetWeight(),
"free_slots": inventory.GetNumberOfFreeSlots(),
"overflow_items": len(inventory.GetOverflowItemList()),
"stat_bonuses": bonuses,
"last_update": time.Now(),
}, nil
}
// GetSystemStats returns comprehensive statistics about the item system
func (isa *ItemSystemAdapter) GetSystemStats() map[string]interface{} {
isa.mutex.RLock()
defer isa.mutex.RUnlock()
masterStats := isa.masterList.GetStats()
return map[string]interface{}{
"total_item_templates": masterStats.TotalItems,
"items_by_type": masterStats.ItemsByType,
"items_by_tier": masterStats.ItemsByTier,
"active_players": len(isa.playerLists),
"cached_inventories": len(isa.playerLists),
"cached_equipment": len(isa.equipmentLists),
"last_update": time.Now(),
}
}
// ClearPlayerData removes cached data for a player (e.g., when they log out)
func (isa *ItemSystemAdapter) ClearPlayerData(playerID uint32) {
isa.mutex.Lock()
defer isa.mutex.Unlock()
// Remove inventory
delete(isa.playerLists, playerID)
// Remove equipment
keysToDelete := make([]uint32, 0)
for key := range isa.equipmentLists {
if key/10 == playerID {
keysToDelete = append(keysToDelete, key)
}
}
for _, key := range keysToDelete {
delete(isa.equipmentLists, key)
}
}
// ValidatePlayerItems validates all items for a player
func (isa *ItemSystemAdapter) ValidatePlayerItems(playerID uint32) *ItemValidationResult {
result := &ItemValidationResult{Valid: true}
// Validate inventory
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
result.Valid = false
result.Errors = append(result.Errors, fmt.Sprintf("Failed to load inventory: %v", err))
return result
}
allItems := inventory.GetAllItems()
for index, item := range allItems {
itemResult := item.Validate()
if !itemResult.Valid {
result.Valid = false
for _, itemErr := range itemResult.Errors {
result.Errors = append(result.Errors, fmt.Sprintf("Inventory item %d: %s", index, itemErr))
}
}
}
// Validate equipment
equipment, err := isa.GetPlayerEquipment(playerID, BaseEquipment)
if err != nil {
result.Valid = false
result.Errors = append(result.Errors, fmt.Sprintf("Failed to load equipment: %v", err))
return result
}
equipResult := equipment.ValidateEquipment()
if !equipResult.Valid {
result.Valid = false
result.Errors = append(result.Errors, equipResult.Errors...)
}
return result
}
// 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
description string
}
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 (ms MockSpell) GetDescription() string { return ms.description }
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) GetSpellsBySkill(skillID uint32) ([]uint32, error) {
return []uint32{}, nil
}
func (msm *MockSpellManager) ValidateSpellID(spellID uint32) bool {
_, exists := msm.spells[spellID]
return exists
}
// 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, description string) {
msm.spells[id] = MockSpell{
id: id,
name: name,
icon: icon,
iconBackdrop: icon + 1000,
tier: tier,
description: description,
}
}
func init() {
log.Printf("Item system interfaces initialized")
}

1009
internal/items/item.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,829 @@
package items
import (
"fmt"
"testing"
)
func TestNewItem(t *testing.T) {
item := NewItem()
if item == nil {
t.Fatal("NewItem returned nil")
}
if item.Details.UniqueID <= 0 {
t.Error("New item should have a valid unique ID")
}
if item.Details.Count != 1 {
t.Errorf("Expected count 1, got %d", item.Details.Count)
}
if item.GenericInfo.Condition != DefaultItemCondition {
t.Errorf("Expected condition %d, got %d", DefaultItemCondition, item.GenericInfo.Condition)
}
}
func TestNewItemFromTemplate(t *testing.T) {
// Create template
template := NewItem()
template.Name = "Test Sword"
template.Description = "A test weapon"
template.Details.ItemID = 12345
template.Details.Icon = 100
template.GenericInfo.ItemType = ItemTypeWeapon
template.WeaponInfo = &WeaponInfo{
WieldType: ItemWieldTypeSingle,
DamageLow1: 10,
DamageHigh1: 20,
Delay: 30,
Rating: 1.5,
}
// Create from template
item := NewItemFromTemplate(template)
if item == nil {
t.Fatal("NewItemFromTemplate returned nil")
}
if item.Name != template.Name {
t.Errorf("Expected name %s, got %s", template.Name, item.Name)
}
if item.Details.ItemID != template.Details.ItemID {
t.Errorf("Expected item ID %d, got %d", template.Details.ItemID, item.Details.ItemID)
}
if item.Details.UniqueID == template.Details.UniqueID {
t.Error("New item should have different unique ID from template")
}
if item.WeaponInfo == nil {
t.Fatal("Weapon info should be copied")
}
if item.WeaponInfo.DamageLow1 != template.WeaponInfo.DamageLow1 {
t.Errorf("Expected damage %d, got %d", template.WeaponInfo.DamageLow1, item.WeaponInfo.DamageLow1)
}
}
func TestItemCopy(t *testing.T) {
original := NewItem()
original.Name = "Original Item"
original.Details.ItemID = 999
original.AddStat(&ItemStat{
StatName: "Strength",
StatType: ItemStatStr,
Value: 10,
})
copy := original.Copy()
if copy == nil {
t.Fatal("Copy returned nil")
}
if copy.Name != original.Name {
t.Errorf("Expected name %s, got %s", original.Name, copy.Name)
}
if copy.Details.UniqueID == original.Details.UniqueID {
t.Error("Copy should have different unique ID")
}
if len(copy.ItemStats) != len(original.ItemStats) {
t.Errorf("Expected %d stats, got %d", len(original.ItemStats), len(copy.ItemStats))
}
// Test nil copy
var nilItem *Item
nilCopy := nilItem.Copy()
if nilCopy != nil {
t.Error("Copy of nil should return nil")
}
}
func TestItemValidation(t *testing.T) {
// Valid item
item := NewItem()
item.Name = "Valid Item"
item.Details.ItemID = 100
result := item.Validate()
if !result.Valid {
t.Errorf("Valid item should pass validation: %v", result.Errors)
}
// Invalid item - no name
invalidItem := NewItem()
invalidItem.Details.ItemID = 100
result = invalidItem.Validate()
if result.Valid {
t.Error("Item without name should fail validation")
}
// Invalid item - negative count
invalidItem2 := NewItem()
invalidItem2.Name = "Invalid Item"
invalidItem2.Details.ItemID = 100
invalidItem2.Details.Count = -1
result = invalidItem2.Validate()
if result.Valid {
t.Error("Item with negative count should fail validation")
}
}
func TestItemStats(t *testing.T) {
item := NewItem()
// Add a stat
stat := &ItemStat{
StatName: "Strength",
StatType: ItemStatStr,
Value: 15,
Level: 1,
}
item.AddStat(stat)
if len(item.ItemStats) != 1 {
t.Errorf("Expected 1 stat, got %d", len(item.ItemStats))
}
// Check if item has stat
if !item.HasStat(0, "Strength") {
t.Error("Item should have Strength stat")
}
if !item.HasStat(uint32(ItemStatStr), "") {
t.Error("Item should have STR stat by ID")
}
if item.HasStat(0, "Nonexistent") {
t.Error("Item should not have nonexistent stat")
}
// Add stat by values
item.AddStatByValues(ItemStatAgi, 0, 10, 1, "Agility")
if len(item.ItemStats) != 2 {
t.Errorf("Expected 2 stats, got %d", len(item.ItemStats))
}
}
func TestItemFlags(t *testing.T) {
item := NewItem()
// Set flags
item.GenericInfo.ItemFlags = Attuned | NoTrade
item.GenericInfo.ItemFlags2 = Heirloom | Ornate
// Test flag checking
if !item.CheckFlag(Attuned) {
t.Error("Item should be attuned")
}
if !item.CheckFlag(NoTrade) {
t.Error("Item should be no-trade")
}
if item.CheckFlag(Lore) {
t.Error("Item should not be lore")
}
if !item.CheckFlag2(Heirloom) {
t.Error("Item should be heirloom")
}
if !item.CheckFlag2(Ornate) {
t.Error("Item should be ornate")
}
if item.CheckFlag2(Refined) {
t.Error("Item should not be refined")
}
}
func TestItemLocking(t *testing.T) {
item := NewItem()
// Item should not be locked initially
if item.IsItemLocked() {
t.Error("New item should not be locked")
}
// Lock for crafting
if !item.TryLockItem(LockReasonCrafting) {
t.Error("Should be able to lock item for crafting")
}
if !item.IsItemLocked() {
t.Error("Item should be locked")
}
if !item.IsItemLockedFor(LockReasonCrafting) {
t.Error("Item should be locked for crafting")
}
if item.IsItemLockedFor(LockReasonHouse) {
t.Error("Item should not be locked for house")
}
// Try to lock for another reason while already locked
if item.TryLockItem(LockReasonHouse) {
t.Error("Should not be able to lock for different reason")
}
// Unlock
if !item.TryUnlockItem(LockReasonCrafting) {
t.Error("Should be able to unlock item")
}
if item.IsItemLocked() {
t.Error("Item should not be locked after unlock")
}
}
func TestItemTypes(t *testing.T) {
item := NewItem()
// Test weapon
item.GenericInfo.ItemType = ItemTypeWeapon
if !item.IsWeapon() {
t.Error("Item should be a weapon")
}
if item.IsArmor() {
t.Error("Item should not be armor")
}
// Test armor
item.GenericInfo.ItemType = ItemTypeArmor
if !item.IsArmor() {
t.Error("Item should be armor")
}
if item.IsWeapon() {
t.Error("Item should not be a weapon")
}
// Test bag
item.GenericInfo.ItemType = ItemTypeBag
if !item.IsBag() {
t.Error("Item should be a bag")
}
// Test food
item.GenericInfo.ItemType = ItemTypeFood
item.FoodInfo = &FoodInfo{Type: 1} // Food
if !item.IsFood() {
t.Error("Item should be food")
}
if !item.IsFoodFood() {
t.Error("Item should be food (not drink)")
}
if item.IsFoodDrink() {
t.Error("Item should not be drink")
}
// Test drink
item.FoodInfo.Type = 0 // Drink
if !item.IsFoodDrink() {
t.Error("Item should be drink")
}
if item.IsFoodFood() {
t.Error("Item should not be food")
}
}
func TestMasterItemList(t *testing.T) {
masterList := NewMasterItemList()
if masterList == nil {
t.Fatal("NewMasterItemList returned nil")
}
// Initial state
if masterList.GetItemCount() != 0 {
t.Error("New master list should be empty")
}
// Add item
item := NewItem()
item.Name = "Test Item"
item.Details.ItemID = 12345
masterList.AddItem(item)
if masterList.GetItemCount() != 1 {
t.Errorf("Expected 1 item, got %d", masterList.GetItemCount())
}
// Get item
retrieved := masterList.GetItem(12345)
if retrieved == nil {
t.Fatal("GetItem returned nil")
}
if retrieved.Name != item.Name {
t.Errorf("Expected name %s, got %s", item.Name, retrieved.Name)
}
// Get by name
byName := masterList.GetItemByName("Test Item")
if byName == nil {
t.Fatal("GetItemByName returned nil")
}
if byName.Details.ItemID != item.Details.ItemID {
t.Errorf("Expected item ID %d, got %d", item.Details.ItemID, byName.Details.ItemID)
}
// Get non-existent item
nonExistent := masterList.GetItem(99999)
if nonExistent != nil {
t.Error("GetItem should return nil for non-existent item")
}
// Test stats
stats := masterList.GetStats()
if stats.TotalItems != 1 {
t.Errorf("Expected 1 total item, got %d", stats.TotalItems)
}
}
func TestMasterItemListStatMapping(t *testing.T) {
masterList := NewMasterItemList()
// Test getting stat ID by name
strID := masterList.GetItemStatIDByName("strength")
if strID == 0 {
t.Error("Should find strength stat ID")
}
// Test getting stat name by ID
strName := masterList.GetItemStatNameByID(ItemStatStr)
if strName == "" {
t.Error("Should find stat name for STR")
}
// Add custom stat mapping
masterList.AddMappedItemStat(9999, "custom stat")
customID := masterList.GetItemStatIDByName("custom stat")
if customID != 9999 {
t.Errorf("Expected custom stat ID 9999, got %d", customID)
}
customName := masterList.GetItemStatNameByID(9999)
if customName != "custom stat" {
t.Errorf("Expected 'custom stat', got '%s'", customName)
}
}
func TestPlayerItemList(t *testing.T) {
playerList := NewPlayerItemList()
if playerList == nil {
t.Fatal("NewPlayerItemList returned nil")
}
// Initial state
if playerList.GetNumberOfItems() != 0 {
t.Error("New player list should be empty")
}
// Create test item
item := NewItem()
item.Name = "Player Item"
item.Details.ItemID = 1001
item.Details.BagID = 0
item.Details.SlotID = 0
// Add item
if !playerList.AddItem(item) {
t.Error("Should be able to add item")
}
if playerList.GetNumberOfItems() != 1 {
t.Errorf("Expected 1 item, got %d", playerList.GetNumberOfItems())
}
// Get item
retrieved := playerList.GetItem(0, 0, BaseEquipment)
if retrieved == nil {
t.Fatal("GetItem returned nil")
}
if retrieved.Name != item.Name {
t.Errorf("Expected name %s, got %s", item.Name, retrieved.Name)
}
// Test HasItem
if !playerList.HasItem(1001, false) {
t.Error("Player should have item 1001")
}
if playerList.HasItem(9999, false) {
t.Error("Player should not have item 9999")
}
// Remove item
playerList.RemoveItem(item, true, true)
if playerList.GetNumberOfItems() != 0 {
t.Errorf("Expected 0 items after removal, got %d", playerList.GetNumberOfItems())
}
}
func TestPlayerItemListOverflow(t *testing.T) {
playerList := NewPlayerItemList()
// Add item to overflow
item := NewItem()
item.Name = "Overflow Item"
item.Details.ItemID = 2001
if !playerList.AddOverflowItem(item) {
t.Error("Should be able to add overflow item")
}
// Check overflow
overflowItem := playerList.GetOverflowItem()
if overflowItem == nil {
t.Fatal("GetOverflowItem returned nil")
}
if overflowItem.Name != item.Name {
t.Errorf("Expected name %s, got %s", item.Name, overflowItem.Name)
}
// Get all overflow items
overflowItems := playerList.GetOverflowItemList()
if len(overflowItems) != 1 {
t.Errorf("Expected 1 overflow item, got %d", len(overflowItems))
}
// Remove overflow item
playerList.RemoveOverflowItem(item)
overflowItems = playerList.GetOverflowItemList()
if len(overflowItems) != 0 {
t.Errorf("Expected 0 overflow items, got %d", len(overflowItems))
}
}
func TestEquipmentItemList(t *testing.T) {
equipment := NewEquipmentItemList()
if equipment == nil {
t.Fatal("NewEquipmentItemList returned nil")
}
// Initial state
if equipment.GetNumberOfItems() != 0 {
t.Error("New equipment list should be empty")
}
// Create test weapon
weapon := NewItem()
weapon.Name = "Test Sword"
weapon.Details.ItemID = 3001
weapon.GenericInfo.ItemType = ItemTypeWeapon
weapon.AddSlot(EQ2PrimarySlot)
// Equip weapon
if !equipment.AddItem(EQ2PrimarySlot, weapon) {
t.Error("Should be able to equip weapon")
}
if equipment.GetNumberOfItems() != 1 {
t.Errorf("Expected 1 equipped item, got %d", equipment.GetNumberOfItems())
}
// Get equipped weapon
equippedWeapon := equipment.GetItem(EQ2PrimarySlot)
if equippedWeapon == nil {
t.Fatal("GetItem returned nil")
}
if equippedWeapon.Name != weapon.Name {
t.Errorf("Expected name %s, got %s", weapon.Name, equippedWeapon.Name)
}
// Test equipment queries
if !equipment.HasItem(3001) {
t.Error("Equipment should have item 3001")
}
if !equipment.HasWeaponEquipped() {
t.Error("Equipment should have weapon equipped")
}
weapons := equipment.GetWeapons()
if len(weapons) != 1 {
t.Errorf("Expected 1 weapon, got %d", len(weapons))
}
// Remove weapon
equipment.RemoveItem(EQ2PrimarySlot, false)
if equipment.GetNumberOfItems() != 0 {
t.Errorf("Expected 0 items after removal, got %d", equipment.GetNumberOfItems())
}
}
func TestEquipmentValidation(t *testing.T) {
equipment := NewEquipmentItemList()
// Create invalid item (no name)
invalidItem := NewItem()
invalidItem.Details.ItemID = 4001
invalidItem.AddSlot(EQ2HeadSlot)
equipment.SetItem(EQ2HeadSlot, invalidItem, false)
result := equipment.ValidateEquipment()
if result.Valid {
t.Error("Equipment with invalid item should fail validation")
}
// Create item that can't be equipped in the slot
wrongSlotItem := NewItem()
wrongSlotItem.Name = "Wrong Slot Item"
wrongSlotItem.Details.ItemID = 4002
wrongSlotItem.AddSlot(EQ2ChestSlot) // Can only go in chest
equipment.SetItem(EQ2HeadSlot, wrongSlotItem, false)
result = equipment.ValidateEquipment()
if result.Valid {
t.Error("Equipment with wrong slot item should fail validation")
}
}
func TestItemSystemAdapter(t *testing.T) {
// Create dependencies
masterList := NewMasterItemList()
spellManager := NewMockSpellManager()
// Add a test spell
spellManager.AddMockSpell(1001, "Test Spell", 100, 1, "A test spell")
adapter := NewItemSystemAdapter(
masterList,
spellManager,
nil, // playerManager
nil, // packetManager
nil, // ruleManager
nil, // databaseService
nil, // questManager
nil, // brokerManager
nil, // craftingManager
nil, // housingManager
nil, // lootManager
)
if adapter == nil {
t.Fatal("NewItemSystemAdapter returned nil")
}
// Test stats
stats := adapter.GetSystemStats()
if stats == nil {
t.Error("GetSystemStats should not return nil")
}
totalTemplates, ok := stats["total_item_templates"].(int32)
if !ok || totalTemplates != 0 {
t.Errorf("Expected 0 total templates, got %v", stats["total_item_templates"])
}
}
func TestItemBrokerChecks(t *testing.T) {
masterList := NewMasterItemList()
// Create weapon
weapon := NewItem()
weapon.Name = "Test Weapon"
weapon.GenericInfo.ItemType = ItemTypeWeapon
weapon.AddSlot(EQ2PrimarySlot)
// Test broker type checks
if !masterList.ShouldAddItemBrokerSlot(weapon, ItemBrokerSlotPrimary) {
t.Error("Weapon should match primary slot broker type")
}
if masterList.ShouldAddItemBrokerSlot(weapon, ItemBrokerSlotHead) {
t.Error("Weapon should not match head slot broker type")
}
// Create armor with stats
armor := NewItem()
armor.Name = "Test Armor"
armor.GenericInfo.ItemType = ItemTypeArmor
armor.AddStat(&ItemStat{
StatName: "Strength",
StatType: ItemStatStr,
Value: 10,
})
if !masterList.ShouldAddItemBrokerStat(armor, ItemBrokerStatTypeStr) {
t.Error("Armor should match STR stat broker type")
}
if masterList.ShouldAddItemBrokerStat(armor, ItemBrokerStatTypeInt) {
t.Error("Armor should not match INT stat broker type")
}
}
func TestItemSearchCriteria(t *testing.T) {
masterList := NewMasterItemList()
// Add test items
sword := NewItem()
sword.Name = "Steel Sword"
sword.Details.ItemID = 5001
sword.Details.Tier = 1
sword.Details.RecommendedLevel = 10
sword.GenericInfo.ItemType = ItemTypeWeapon
sword.BrokerPrice = 1000
armor := NewItem()
armor.Name = "Iron Armor"
armor.Details.ItemID = 5002
armor.Details.Tier = 2
armor.Details.RecommendedLevel = 15
armor.GenericInfo.ItemType = ItemTypeArmor
armor.BrokerPrice = 2000
masterList.AddItem(sword)
masterList.AddItem(armor)
// Search by name
criteria := &ItemSearchCriteria{
Name: "sword",
}
results := masterList.GetItems(criteria)
if len(results) != 1 {
t.Errorf("Expected 1 result for sword search, got %d", len(results))
}
if results[0].Name != sword.Name {
t.Errorf("Expected %s, got %s", sword.Name, results[0].Name)
}
// Search by price range
criteria = &ItemSearchCriteria{
MinPrice: 1500,
MaxPrice: 2500,
}
results = masterList.GetItems(criteria)
if len(results) != 1 {
t.Errorf("Expected 1 result for price search, got %d", len(results))
}
if results[0].Name != armor.Name {
t.Errorf("Expected %s, got %s", armor.Name, results[0].Name)
}
// Search by tier
criteria = &ItemSearchCriteria{
MinTier: 2,
MaxTier: 2,
}
results = masterList.GetItems(criteria)
if len(results) != 1 {
t.Errorf("Expected 1 result for tier search, got %d", len(results))
}
// Search with no matches
criteria = &ItemSearchCriteria{
Name: "nonexistent",
}
results = masterList.GetItems(criteria)
if len(results) != 0 {
t.Errorf("Expected 0 results for nonexistent search, got %d", len(results))
}
}
func TestNextUniqueID(t *testing.T) {
id1 := NextUniqueID()
id2 := NextUniqueID()
if id1 == id2 {
t.Error("NextUniqueID should return different IDs")
}
if id2 != id1+1 {
t.Errorf("Expected ID2 to be ID1+1, got %d and %d", id1, id2)
}
}
func TestItemError(t *testing.T) {
err := NewItemError("test error")
if err == nil {
t.Fatal("NewItemError returned nil")
}
if err.Error() != "test error" {
t.Errorf("Expected 'test error', got '%s'", err.Error())
}
if !IsItemError(err) {
t.Error("Should identify as item error")
}
// Test with non-item error
if IsItemError(fmt.Errorf("not an item error")) {
t.Error("Should not identify as item error")
}
}
func TestConstants(t *testing.T) {
// Test slot constants
if EQ2PrimarySlot != 0 {
t.Errorf("Expected EQ2PrimarySlot to be 0, got %d", EQ2PrimarySlot)
}
if NumSlots != 25 {
t.Errorf("Expected NumSlots to be 25, got %d", NumSlots)
}
// Test item type constants
if ItemTypeWeapon != 1 {
t.Errorf("Expected ItemTypeWeapon to be 1, got %d", ItemTypeWeapon)
}
// Test flag constants
if Attuned != 1 {
t.Errorf("Expected Attuned to be 1, got %d", Attuned)
}
// Test stat constants
if ItemStatStr != 0 {
t.Errorf("Expected ItemStatStr to be 0, got %d", ItemStatStr)
}
}
func BenchmarkItemCreation(b *testing.B) {
for i := 0; i < b.N; i++ {
item := NewItem()
item.Name = "Benchmark Item"
item.Details.ItemID = int32(i)
}
}
func BenchmarkMasterItemListAccess(b *testing.B) {
masterList := NewMasterItemList()
// Add test items
for i := 0; i < 1000; i++ {
item := NewItem()
item.Name = fmt.Sprintf("Item %d", i)
item.Details.ItemID = int32(i + 1000)
masterList.AddItem(item)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
masterList.GetItem(int32((i % 1000) + 1000))
}
}
func BenchmarkPlayerItemListAdd(b *testing.B) {
playerList := NewPlayerItemList()
b.ResetTimer()
for i := 0; i < b.N; i++ {
item := NewItem()
item.Name = fmt.Sprintf("Item %d", i)
item.Details.ItemID = int32(i)
item.Details.BagID = int32(i % 6)
item.Details.SlotID = int16(i % 20)
playerList.AddItem(item)
}
}
func BenchmarkEquipmentBonusCalculation(b *testing.B) {
equipment := NewEquipmentItemList()
// Add some equipped items with stats
for slot := 0; slot < 10; slot++ {
item := NewItem()
item.Name = fmt.Sprintf("Equipment %d", slot)
item.Details.ItemID = int32(slot + 6000)
item.AddSlot(int8(slot))
// Add some stats
item.AddStat(&ItemStat{StatType: ItemStatStr, Value: 10})
item.AddStat(&ItemStat{StatType: ItemStatAgi, Value: 5})
item.AddStat(&ItemStat{StatType: ItemStatHealth, Value: 100})
equipment.SetItem(int8(slot), item, false)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
equipment.CalculateEquipmentBonuses()
}
}

View File

@ -0,0 +1,688 @@
package items
import (
"fmt"
"log"
"strings"
"time"
)
// NewMasterItemList creates a new master item list
func NewMasterItemList() *MasterItemList {
mil := &MasterItemList{
items: make(map[int32]*Item),
mappedItemStatsStrings: make(map[string]int32),
mappedItemStatTypeIDs: make(map[int32]string),
brokerItemMap: make(map[*VersionRange]map[int64]int64),
}
// Initialize mapped item stats
mil.initializeMappedStats()
return mil
}
// initializeMappedStats initializes the mapped item stats
func (mil *MasterItemList) initializeMappedStats() {
// Add all the mapped item stats as in the C++ constructor
mil.AddMappedItemStat(ItemStatAdorning, "adorning")
mil.AddMappedItemStat(ItemStatAggression, "aggression")
mil.AddMappedItemStat(ItemStatArtificing, "artificing")
mil.AddMappedItemStat(ItemStatArtistry, "artistry")
mil.AddMappedItemStat(ItemStatChemistry, "chemistry")
mil.AddMappedItemStat(ItemStatCrushing, "crushing")
mil.AddMappedItemStat(ItemStatDefense, "defense")
mil.AddMappedItemStat(ItemStatDeflection, "deflection")
mil.AddMappedItemStat(ItemStatDisruption, "disruption")
mil.AddMappedItemStat(ItemStatFishing, "fishing")
mil.AddMappedItemStat(ItemStatFletching, "fletching")
mil.AddMappedItemStat(ItemStatFocus, "focus")
mil.AddMappedItemStat(ItemStatForesting, "foresting")
mil.AddMappedItemStat(ItemStatGathering, "gathering")
mil.AddMappedItemStat(ItemStatMetalShaping, "metal shaping")
mil.AddMappedItemStat(ItemStatMetalworking, "metalworking")
mil.AddMappedItemStat(ItemStatMining, "mining")
mil.AddMappedItemStat(ItemStatMinistration, "ministration")
mil.AddMappedItemStat(ItemStatOrdination, "ordination")
mil.AddMappedItemStat(ItemStatParry, "parry")
mil.AddMappedItemStat(ItemStatPiercing, "piercing")
mil.AddMappedItemStat(ItemStatRanged, "ranged")
mil.AddMappedItemStat(ItemStatSafeFall, "safe fall")
mil.AddMappedItemStat(ItemStatScribing, "scribing")
mil.AddMappedItemStat(ItemStatSculpting, "sculpting")
mil.AddMappedItemStat(ItemStatSlashing, "slashing")
mil.AddMappedItemStat(ItemStatSubjugation, "subjugation")
mil.AddMappedItemStat(ItemStatSwimming, "swimming")
mil.AddMappedItemStat(ItemStatTailoring, "tailoring")
mil.AddMappedItemStat(ItemStatTinkering, "tinkering")
mil.AddMappedItemStat(ItemStatTransmuting, "transmuting")
mil.AddMappedItemStat(ItemStatTrapping, "trapping")
mil.AddMappedItemStat(ItemStatWeaponSkills, "weapon skills")
mil.AddMappedItemStat(ItemStatPowerCostReduction, "power cost reduction")
mil.AddMappedItemStat(ItemStatSpellAvoidance, "spell avoidance")
}
// AddMappedItemStat adds a mapping between stat ID and name
func (mil *MasterItemList) AddMappedItemStat(id int32, lowerCaseName string) {
mil.mutex.Lock()
defer mil.mutex.Unlock()
mil.mappedItemStatsStrings[lowerCaseName] = id
mil.mappedItemStatTypeIDs[id] = lowerCaseName
}
// GetItemStatIDByName gets the stat ID by name
func (mil *MasterItemList) GetItemStatIDByName(name string) int32 {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
lowerName := strings.ToLower(name)
if id, exists := mil.mappedItemStatsStrings[lowerName]; exists {
return id
}
return 0
}
// GetItemStatNameByID gets the stat name by ID
func (mil *MasterItemList) GetItemStatNameByID(id int32) string {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
if name, exists := mil.mappedItemStatTypeIDs[id]; exists {
return name
}
return ""
}
// AddItem adds an item to the master list
func (mil *MasterItemList) AddItem(item *Item) {
if item == nil {
return
}
mil.mutex.Lock()
defer mil.mutex.Unlock()
mil.items[item.Details.ItemID] = item
log.Printf("Added item %d (%s) to master list", item.Details.ItemID, item.Name)
}
// GetItem retrieves an item by ID
func (mil *MasterItemList) GetItem(itemID int32) *Item {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
if item, exists := mil.items[itemID]; exists {
return item.Copy() // Return a copy to prevent external modifications
}
return nil
}
// GetItemByName retrieves an item by name (case-insensitive)
func (mil *MasterItemList) GetItemByName(name string) *Item {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
lowerName := strings.ToLower(name)
for _, item := range mil.items {
if strings.ToLower(item.Name) == lowerName {
return item.Copy()
}
}
return nil
}
// IsBag checks if an item ID represents a bag
func (mil *MasterItemList) IsBag(itemID int32) bool {
item := mil.GetItem(itemID)
if item == nil {
return false
}
return item.IsBag()
}
// RemoveAll removes all items from the master list
func (mil *MasterItemList) RemoveAll() {
mil.mutex.Lock()
defer mil.mutex.Unlock()
count := len(mil.items)
mil.items = make(map[int32]*Item)
log.Printf("Removed %d items from master list", count)
}
// GetItemCount returns the total number of items
func (mil *MasterItemList) GetItemCount() int {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
return len(mil.items)
}
// CalculateItemBonuses calculates the stat bonuses for an item
func (mil *MasterItemList) CalculateItemBonuses(itemID int32) *ItemStatsValues {
item := mil.GetItem(itemID)
if item == nil {
return nil
}
return mil.CalculateItemBonusesFromItem(item)
}
// CalculateItemBonusesFromItem calculates stat bonuses from an item instance
func (mil *MasterItemList) CalculateItemBonusesFromItem(item *Item) *ItemStatsValues {
if item == nil {
return nil
}
item.mutex.RLock()
defer item.mutex.RUnlock()
values := &ItemStatsValues{}
// Process all item stats
for _, stat := range item.ItemStats {
switch stat.StatType {
case ItemStatStr:
values.Str += int16(stat.Value)
case ItemStatSta:
values.Sta += int16(stat.Value)
case ItemStatAgi:
values.Agi += int16(stat.Value)
case ItemStatWis:
values.Wis += int16(stat.Value)
case ItemStatInt:
values.Int += int16(stat.Value)
case ItemStatVsSlash:
values.VsSlash += int16(stat.Value)
case ItemStatVsCrush:
values.VsCrush += int16(stat.Value)
case ItemStatVsPierce:
values.VsPierce += int16(stat.Value)
case ItemStatVsPhysical:
values.VsPhysical += int16(stat.Value)
case ItemStatVsHeat:
values.VsHeat += int16(stat.Value)
case ItemStatVsCold:
values.VsCold += int16(stat.Value)
case ItemStatVsMagic:
values.VsMagic += int16(stat.Value)
case ItemStatVsMental:
values.VsMental += int16(stat.Value)
case ItemStatVsDivine:
values.VsDivine += int16(stat.Value)
case ItemStatVsDisease:
values.VsDisease += int16(stat.Value)
case ItemStatVsPoison:
values.VsPoison += int16(stat.Value)
case ItemStatHealth:
values.Health += int16(stat.Value)
case ItemStatPower:
values.Power += int16(stat.Value)
case ItemStatConcentration:
values.Concentration += int8(stat.Value)
case ItemStatAbilityModifier:
values.AbilityModifier += int16(stat.Value)
case ItemStatCriticalMitigation:
values.CriticalMitigation += int16(stat.Value)
case ItemStatExtraShieldBlockChance:
values.ExtraShieldBlockChance += int16(stat.Value)
case ItemStatBeneficialCritChance:
values.BeneficialCritChance += int16(stat.Value)
case ItemStatCritBonus:
values.CritBonus += int16(stat.Value)
case ItemStatPotency:
values.Potency += int16(stat.Value)
case ItemStatHateGainMod:
values.HateGainMod += int16(stat.Value)
case ItemStatAbilityReuseSpeed:
values.AbilityReuseSpeed += int16(stat.Value)
case ItemStatAbilityCastingSpeed:
values.AbilityCastingSpeed += int16(stat.Value)
case ItemStatAbilityRecoverySpeed:
values.AbilityRecoverySpeed += int16(stat.Value)
case ItemStatSpellReuseSpeed:
values.SpellReuseSpeed += int16(stat.Value)
case ItemStatSpellMultiAttackChance:
values.SpellMultiAttackChance += int16(stat.Value)
case ItemStatDPS:
values.DPS += int16(stat.Value)
case ItemStatAttackSpeed:
values.AttackSpeed += int16(stat.Value)
case ItemStatMultiattackChance:
values.MultiAttackChance += int16(stat.Value)
case ItemStatFlurry:
values.Flurry += int16(stat.Value)
case ItemStatAEAutoattackChance:
values.AEAutoattackChance += int16(stat.Value)
case ItemStatStrikethrough:
values.Strikethrough += int16(stat.Value)
case ItemStatAccuracy:
values.Accuracy += int16(stat.Value)
case ItemStatOffensiveSpeed:
values.OffensiveSpeed += int16(stat.Value)
case ItemStatUncontestedParry:
values.UncontestedParry += stat.Value
case ItemStatUncontestedBlock:
values.UncontestedBlock += stat.Value
case ItemStatUncontestedDodge:
values.UncontestedDodge += stat.Value
case ItemStatUncontestedRiposte:
values.UncontestedRiposte += stat.Value
case ItemStatSizeMod:
values.SizeMod += stat.Value
}
}
return values
}
// Broker-related methods
// AddBrokerItemMapRange adds a broker item mapping range
func (mil *MasterItemList) AddBrokerItemMapRange(minVersion int32, maxVersion int32, clientBitmask int64, serverBitmask int64) {
mil.mutex.Lock()
defer mil.mutex.Unlock()
// Find existing range
var targetRange *VersionRange
for versionRange := range mil.brokerItemMap {
if versionRange.MinVersion == minVersion && versionRange.MaxVersion == maxVersion {
targetRange = versionRange
break
}
}
// Create new range if not found
if targetRange == nil {
targetRange = &VersionRange{
MinVersion: minVersion,
MaxVersion: maxVersion,
}
mil.brokerItemMap[targetRange] = make(map[int64]int64)
}
mil.brokerItemMap[targetRange][clientBitmask] = serverBitmask
}
// FindBrokerItemMapVersionRange finds a broker item map by version range
func (mil *MasterItemList) FindBrokerItemMapVersionRange(minVersion int32, maxVersion int32) map[int64]int64 {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
for versionRange, mapping := range mil.brokerItemMap {
// Check if min and max version are both in range
if versionRange.MinVersion <= minVersion && maxVersion <= versionRange.MaxVersion {
return mapping
}
// Check if the min version is in range, but max range is 0
if versionRange.MinVersion <= minVersion && versionRange.MaxVersion == 0 {
return mapping
}
// Check if min version is 0 and max_version has a cap
if versionRange.MinVersion == 0 && maxVersion <= versionRange.MaxVersion {
return mapping
}
}
return nil
}
// FindBrokerItemMapByVersion finds a broker item map by specific version
func (mil *MasterItemList) FindBrokerItemMapByVersion(version int32) map[int64]int64 {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
var defaultMapping map[int64]int64
for versionRange, mapping := range mil.brokerItemMap {
// Check for default range (0,0)
if versionRange.MinVersion == 0 && versionRange.MaxVersion == 0 {
defaultMapping = mapping
continue
}
// Check if version is in range
if version >= versionRange.MinVersion && version <= versionRange.MaxVersion {
return mapping
}
}
return defaultMapping
}
// ShouldAddItemBrokerType checks if an item should be added to broker by type
func (mil *MasterItemList) ShouldAddItemBrokerType(item *Item, itemType int64) bool {
if item == nil {
return false
}
switch itemType {
case ItemBrokerTypeAdornment:
return item.IsAdornment()
case ItemBrokerTypeAmmo:
return item.IsAmmo()
case ItemBrokerTypeAttuneable:
return item.CheckFlag(Attuneable)
case ItemBrokerTypeBag:
return item.IsBag()
case ItemBrokerTypeBauble:
return item.IsBauble()
case ItemBrokerTypeBook:
return item.IsBook()
case ItemBrokerTypeChainarmor:
return item.IsChainArmor()
case ItemBrokerTypeCloak:
return item.IsCloak()
case ItemBrokerTypeClotharmor:
return item.IsClothArmor()
case ItemBrokerTypeCollectable:
return item.IsCollectable()
case ItemBrokerTypeCrushweapon:
return item.IsCrushWeapon()
case ItemBrokerTypeDrink:
return item.IsFoodDrink()
case ItemBrokerTypeFood:
return item.IsFoodFood()
case ItemBrokerTypeHouseitem:
return item.IsHouseItem()
case ItemBrokerTypeJewelry:
return item.IsJewelry()
case ItemBrokerTypeLeatherarmor:
return item.IsLeatherArmor()
case ItemBrokerTypeLore:
return item.CheckFlag(Lore)
case ItemBrokerTypeMisc:
return item.IsMisc()
case ItemBrokerTypePierceweapon:
return item.IsPierceWeapon()
case ItemBrokerTypePlatearmor:
return item.IsPlateArmor()
case ItemBrokerTypePoison:
return item.IsPoison()
case ItemBrokerTypePotion:
return item.IsPotion()
case ItemBrokerTypeRecipebook:
return item.IsRecipeBook()
case ItemBrokerTypeSalesdisplay:
return item.IsSalesDisplay()
case ItemBrokerTypeShield:
return item.IsShield()
case ItemBrokerTypeSlashweapon:
return item.IsSlashWeapon()
case ItemBrokerTypeSpellscroll:
return item.IsSpellScroll()
case ItemBrokerTypeTinkered:
return item.IsTinkered()
case ItemBrokerTypeTradeskill:
return item.IsTradeskill()
}
return false
}
// ShouldAddItemBrokerSlot checks if an item should be added to broker by slot
func (mil *MasterItemList) ShouldAddItemBrokerSlot(item *Item, slotType int64) bool {
if item == nil {
return false
}
switch slotType {
case ItemBrokerSlotPrimary:
return item.HasSlot(EQ2PrimarySlot, -1)
case ItemBrokerSlotPrimary2H:
return item.HasSlot(EQ2PrimarySlot, -1) || item.HasSlot(EQ2SecondarySlot, -1)
case ItemBrokerSlotSecondary:
return item.HasSlot(EQ2SecondarySlot, -1)
case ItemBrokerSlotHead:
return item.HasSlot(EQ2HeadSlot, -1)
case ItemBrokerSlotChest:
return item.HasSlot(EQ2ChestSlot, -1)
case ItemBrokerSlotShoulders:
return item.HasSlot(EQ2ShouldersSlot, -1)
case ItemBrokerSlotForearms:
return item.HasSlot(EQ2ForearmsSlot, -1)
case ItemBrokerSlotHands:
return item.HasSlot(EQ2HandsSlot, -1)
case ItemBrokerSlotLegs:
return item.HasSlot(EQ2LegsSlot, -1)
case ItemBrokerSlotFeet:
return item.HasSlot(EQ2FeetSlot, -1)
case ItemBrokerSlotRing:
return item.HasSlot(EQ2LRingSlot, EQ2RRingSlot)
case ItemBrokerSlotEars:
return item.HasSlot(EQ2EarsSlot1, EQ2EarsSlot2)
case ItemBrokerSlotNeck:
return item.HasSlot(EQ2NeckSlot, -1)
case ItemBrokerSlotWrist:
return item.HasSlot(EQ2LWristSlot, EQ2RWristSlot)
case ItemBrokerSlotRangeWeapon:
return item.HasSlot(EQ2RangeSlot, -1)
case ItemBrokerSlotAmmo:
return item.HasSlot(EQ2AmmoSlot, -1)
case ItemBrokerSlotWaist:
return item.HasSlot(EQ2WaistSlot, -1)
case ItemBrokerSlotCloak:
return item.HasSlot(EQ2CloakSlot, -1)
case ItemBrokerSlotCharm:
return item.HasSlot(EQ2CharmSlot1, EQ2CharmSlot2)
case ItemBrokerSlotFood:
return item.HasSlot(EQ2FoodSlot, -1)
case ItemBrokerSlotDrink:
return item.HasSlot(EQ2DrinkSlot, -1)
}
return false
}
// ShouldAddItemBrokerStat checks if an item should be added to broker by stat
func (mil *MasterItemList) ShouldAddItemBrokerStat(item *Item, statType int64) bool {
if item == nil {
return false
}
// Check if the item has the requested stat type
for _, stat := range item.ItemStats {
switch statType {
case ItemBrokerStatTypeStr:
if stat.StatType == ItemStatStr {
return true
}
case ItemBrokerStatTypeSta:
if stat.StatType == ItemStatSta {
return true
}
case ItemBrokerStatTypeAgi:
if stat.StatType == ItemStatAgi {
return true
}
case ItemBrokerStatTypeWis:
if stat.StatType == ItemStatWis {
return true
}
case ItemBrokerStatTypeInt:
if stat.StatType == ItemStatInt {
return true
}
case ItemBrokerStatTypeHealth:
if stat.StatType == ItemStatHealth {
return true
}
case ItemBrokerStatTypePower:
if stat.StatType == ItemStatPower {
return true
}
case ItemBrokerStatTypePotency:
if stat.StatType == ItemStatPotency {
return true
}
case ItemBrokerStatTypeCritical:
if stat.StatType == ItemStatMeleeCritChance || stat.StatType == ItemStatBeneficialCritChance {
return true
}
case ItemBrokerStatTypeAttackspeed:
if stat.StatType == ItemStatAttackSpeed {
return true
}
case ItemBrokerStatTypeDPS:
if stat.StatType == ItemStatDPS {
return true
}
// Add more stat type checks as needed
}
}
return false
}
// GetItems searches for items based on criteria
func (mil *MasterItemList) GetItems(criteria *ItemSearchCriteria) []*Item {
if criteria == nil {
return nil
}
mil.mutex.RLock()
defer mil.mutex.RUnlock()
var results []*Item
for _, item := range mil.items {
if mil.matchesCriteria(item, criteria) {
results = append(results, item.Copy())
}
}
return results
}
// matchesCriteria checks if an item matches the search criteria
func (mil *MasterItemList) matchesCriteria(item *Item, criteria *ItemSearchCriteria) bool {
// Name matching
if criteria.Name != "" {
if !strings.Contains(strings.ToLower(item.Name), strings.ToLower(criteria.Name)) {
return false
}
}
// Price range
if criteria.MinPrice > 0 && item.BrokerPrice < criteria.MinPrice {
return false
}
if criteria.MaxPrice > 0 && item.BrokerPrice > criteria.MaxPrice {
return false
}
// Tier range
if criteria.MinTier > 0 && item.Details.Tier < criteria.MinTier {
return false
}
if criteria.MaxTier > 0 && item.Details.Tier > criteria.MaxTier {
return false
}
// Level range
if criteria.MinLevel > 0 && item.Details.RecommendedLevel < criteria.MinLevel {
return false
}
if criteria.MaxLevel > 0 && item.Details.RecommendedLevel > criteria.MaxLevel {
return false
}
// Item type matching
if criteria.ItemType != 0 {
if !mil.ShouldAddItemBrokerType(item, criteria.ItemType) {
return false
}
}
// Location type matching (slot compatibility)
if criteria.LocationType != 0 {
if !mil.ShouldAddItemBrokerSlot(item, criteria.LocationType) {
return false
}
}
// Broker type matching (stat requirements)
if criteria.BrokerType != 0 {
if !mil.ShouldAddItemBrokerStat(item, criteria.BrokerType) {
return false
}
}
// Seller matching
if criteria.Seller != "" {
if !strings.Contains(strings.ToLower(item.SellerName), strings.ToLower(criteria.Seller)) {
return false
}
}
// Adornment matching
if criteria.Adornment != "" {
if !strings.Contains(strings.ToLower(item.Adornment), strings.ToLower(criteria.Adornment)) {
return false
}
}
return true
}
// GetStats returns statistics about the master item list
func (mil *MasterItemList) GetStats() *ItemManagerStats {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
stats := &ItemManagerStats{
TotalItems: int32(len(mil.items)),
ItemsByType: make(map[int8]int32),
ItemsByTier: make(map[int8]int32),
LastUpdate: time.Now(),
}
// Count items by type and tier
for _, item := range mil.items {
stats.ItemsByType[item.GenericInfo.ItemType]++
stats.ItemsByTier[item.Details.Tier]++
}
return stats
}
// Validate validates the master item list
func (mil *MasterItemList) Validate() *ItemValidationResult {
mil.mutex.RLock()
defer mil.mutex.RUnlock()
result := &ItemValidationResult{Valid: true}
for itemID, item := range mil.items {
itemResult := item.Validate()
if !itemResult.Valid {
result.Valid = false
for _, err := range itemResult.Errors {
result.Errors = append(result.Errors, fmt.Sprintf("Item %d: %s", itemID, err))
}
}
}
return result
}
// Size returns the number of items in the master list
func (mil *MasterItemList) Size() int {
return mil.GetItemCount()
}
// Clear removes all items from the master list
func (mil *MasterItemList) Clear() {
mil.RemoveAll()
}
func init() {
log.Printf("Master item list system initialized")
}

View File

@ -0,0 +1,962 @@
package items
import (
"fmt"
"log"
)
// NewPlayerItemList creates a new player item list
func NewPlayerItemList() *PlayerItemList {
return &PlayerItemList{
indexedItems: make(map[int32]*Item),
items: make(map[int32]map[int8]map[int16]*Item),
overflowItems: make([]*Item, 0),
}
}
// SetMaxItemIndex sets and returns the maximum saved item index
func (pil *PlayerItemList) SetMaxItemIndex() int32 {
pil.mutex.Lock()
defer pil.mutex.Unlock()
maxIndex := int32(0)
for index := range pil.indexedItems {
if index > maxIndex {
maxIndex = index
}
}
pil.maxSavedIndex = maxIndex
return maxIndex
}
// SharedBankAddAllowed checks if an item can be added to shared bank
func (pil *PlayerItemList) SharedBankAddAllowed(item *Item) bool {
if item == nil {
return false
}
// Check item flags that prevent shared bank storage
if item.CheckFlag(NoTrade) || item.CheckFlag(Attuned) || item.CheckFlag(LoreEquip) {
return false
}
// Check heirloom flag
if item.CheckFlag2(Heirloom) {
return true // Heirloom items can go in shared bank
}
return true
}
// GetItemsFromBagID gets all items from a specific bag
func (pil *PlayerItemList) GetItemsFromBagID(bagID int32) []*Item {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
var bagItems []*Item
if bagMap, exists := pil.items[bagID]; exists {
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil {
bagItems = append(bagItems, item)
}
}
}
}
return bagItems
}
// GetItemsInBag gets all items inside a bag item
func (pil *PlayerItemList) GetItemsInBag(bag *Item) []*Item {
if bag == nil || !bag.IsBag() {
return nil
}
return pil.GetItemsFromBagID(bag.Details.BagID)
}
// GetBag gets a bag from an inventory slot
func (pil *PlayerItemList) GetBag(inventorySlot int8, lock bool) *Item {
if lock {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
}
// Check main inventory slots
for bagID := int32(0); bagID < NumInvSlots; bagID++ {
if bagMap, exists := pil.items[bagID]; exists {
if slot0Map, exists := bagMap[0]; exists {
if item, exists := slot0Map[int16(inventorySlot)]; exists && item != nil && item.IsBag() {
return item
}
}
}
}
return nil
}
// HasItem checks if the player has a specific item
func (pil *PlayerItemList) HasItem(itemID int32, includeBank bool) bool {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
for bagID, bagMap := range pil.items {
// Skip bank slots if not including bank
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
continue
}
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil && item.Details.ItemID == itemID {
return true
}
}
}
}
return false
}
// GetItemFromIndex gets an item by its index
func (pil *PlayerItemList) GetItemFromIndex(index int32) *Item {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
if item, exists := pil.indexedItems[index]; exists {
return item
}
return nil
}
// MoveItem moves an item to a new location
func (pil *PlayerItemList) MoveItem(item *Item, invSlot int32, slot int16, appearanceType int8, eraseOld bool) {
if item == nil {
return
}
pil.mutex.Lock()
defer pil.mutex.Unlock()
// Remove from old location if requested
if eraseOld {
pil.eraseItemInternal(item)
}
// Update item location
item.Details.InvSlotID = invSlot
item.Details.SlotID = slot
item.Details.AppearanceType = int16(appearanceType)
// Add to new location
pil.addItemToLocationInternal(item, invSlot, appearanceType, slot)
}
// MoveItemByIndex moves an item by index to a new location
func (pil *PlayerItemList) MoveItemByIndex(toBagID int32, fromIndex int16, to int8, appearanceType int8, charges int8) bool {
pil.mutex.Lock()
defer pil.mutex.Unlock()
// Find item by index
var item *Item
for _, bagMap := range pil.items {
for _, slotMap := range bagMap {
for _, foundItem := range slotMap {
if foundItem != nil && foundItem.Details.NewIndex == fromIndex {
item = foundItem
break
}
}
if item != nil {
break
}
}
if item != nil {
break
}
}
if item == nil {
return false
}
// Remove from old location
pil.eraseItemInternal(item)
// Update item properties
item.Details.BagID = toBagID
item.Details.SlotID = int16(to)
item.Details.AppearanceType = int16(appearanceType)
if charges > 0 {
item.Details.Count = int16(charges)
}
// Add to new location
pil.addItemToLocationInternal(item, toBagID, appearanceType, int16(to))
return true
}
// EraseItem removes an item from the inventory
func (pil *PlayerItemList) EraseItem(item *Item) {
if item == nil {
return
}
pil.mutex.Lock()
defer pil.mutex.Unlock()
pil.eraseItemInternal(item)
}
// eraseItemInternal removes an item from internal storage (assumes lock is held)
func (pil *PlayerItemList) eraseItemInternal(item *Item) {
if item == nil {
return
}
// Remove from indexed items
for index, indexedItem := range pil.indexedItems {
if indexedItem == item {
delete(pil.indexedItems, index)
break
}
}
// Remove from location-based storage
if bagMap, exists := pil.items[item.Details.BagID]; exists {
if slotMap, exists := bagMap[int8(item.Details.AppearanceType)]; exists {
delete(slotMap, item.Details.SlotID)
// Clean up empty maps
if len(slotMap) == 0 {
delete(bagMap, int8(item.Details.AppearanceType))
if len(bagMap) == 0 {
delete(pil.items, item.Details.BagID)
}
}
}
}
// Remove from overflow items
for i, overflowItem := range pil.overflowItems {
if overflowItem == item {
pil.overflowItems = append(pil.overflowItems[:i], pil.overflowItems[i+1:]...)
break
}
}
}
// GetItemFromUniqueID gets an item by its unique ID
func (pil *PlayerItemList) GetItemFromUniqueID(uniqueID int32, includeBank bool, lock bool) *Item {
if lock {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
}
for bagID, bagMap := range pil.items {
// Skip bank slots if not including bank
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
continue
}
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil && int32(item.Details.UniqueID) == uniqueID {
return item
}
}
}
}
// Check overflow items
for _, item := range pil.overflowItems {
if item != nil && int32(item.Details.UniqueID) == uniqueID {
return item
}
}
return nil
}
// GetItemFromID gets an item by its template ID
func (pil *PlayerItemList) GetItemFromID(itemID int32, count int8, includeBank bool, lock bool) *Item {
if lock {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
}
for bagID, bagMap := range pil.items {
// Skip bank slots if not including bank
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
continue
}
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil && item.Details.ItemID == itemID {
if count == 0 || item.Details.Count >= int16(count) {
return item
}
}
}
}
}
return nil
}
// GetAllStackCountItemFromID gets the total count of all stacks of an item
func (pil *PlayerItemList) GetAllStackCountItemFromID(itemID int32, count int8, includeBank bool, lock bool) int32 {
if lock {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
}
totalCount := int32(0)
for bagID, bagMap := range pil.items {
// Skip bank slots if not including bank
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
continue
}
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil && item.Details.ItemID == itemID {
totalCount += int32(item.Details.Count)
}
}
}
}
return totalCount
}
// AssignItemToFreeSlot assigns an item to the first available free slot
func (pil *PlayerItemList) AssignItemToFreeSlot(item *Item, inventoryOnly bool) bool {
if item == nil {
return false
}
pil.mutex.Lock()
defer pil.mutex.Unlock()
var bagID int32
var slot int16
if pil.getFirstFreeSlotInternal(&bagID, &slot, inventoryOnly) {
item.Details.BagID = bagID
item.Details.SlotID = slot
item.Details.AppearanceType = BaseEquipment
pil.addItemToLocationInternal(item, bagID, BaseEquipment, slot)
return true
}
return false
}
// GetNumberOfFreeSlots returns the number of free inventory slots
func (pil *PlayerItemList) GetNumberOfFreeSlots() int16 {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
freeSlots := int16(0)
// Check main inventory slots
for bagID := int32(0); bagID < NumInvSlots; bagID++ {
bag := pil.GetBag(int8(bagID), false)
if bag != nil && bag.BagInfo != nil {
// Count free slots in this bag
usedSlots := 0
if bagMap, exists := pil.items[bagID]; exists {
for _, slotMap := range bagMap {
usedSlots += len(slotMap)
}
}
freeSlots += int16(bag.BagInfo.NumSlots) - int16(usedSlots)
}
}
return freeSlots
}
// GetNumberOfItems returns the total number of items in inventory
func (pil *PlayerItemList) GetNumberOfItems() int16 {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
itemCount := int16(0)
for _, bagMap := range pil.items {
for _, slotMap := range bagMap {
itemCount += int16(len(slotMap))
}
}
return itemCount
}
// GetWeight returns the total weight of all items
func (pil *PlayerItemList) GetWeight() int32 {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
totalWeight := int32(0)
for _, bagMap := range pil.items {
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil {
totalWeight += item.GenericInfo.Weight * int32(item.Details.Count)
}
}
}
}
return totalWeight
}
// HasFreeSlot checks if there's at least one free slot
func (pil *PlayerItemList) HasFreeSlot() bool {
return pil.GetNumberOfFreeSlots() > 0
}
// HasFreeBagSlot checks if there's a free bag slot in main inventory
func (pil *PlayerItemList) HasFreeBagSlot() bool {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
// Check main inventory bag slots
for bagSlot := int8(0); bagSlot < NumInvSlots; bagSlot++ {
bag := pil.GetBag(bagSlot, false)
if bag == nil {
return true // Empty bag slot
}
}
return false
}
// DestroyItem destroys an item by index
func (pil *PlayerItemList) DestroyItem(index int16) {
pil.mutex.Lock()
defer pil.mutex.Unlock()
// Find and remove item by index
for _, bagMap := range pil.items {
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil && item.Details.NewIndex == index {
pil.eraseItemInternal(item)
return
}
}
}
}
}
// CanStack checks if an item can be stacked with existing items
func (pil *PlayerItemList) CanStack(item *Item, includeBank bool) *Item {
if item == nil {
return nil
}
pil.mutex.RLock()
defer pil.mutex.RUnlock()
for bagID, bagMap := range pil.items {
// Skip bank slots if not including bank
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
continue
}
for _, slotMap := range bagMap {
for _, existingItem := range slotMap {
if existingItem != nil &&
existingItem.Details.ItemID == item.Details.ItemID &&
existingItem.Details.Count < existingItem.StackCount &&
existingItem.Details.UniqueID != item.Details.UniqueID {
return existingItem
}
}
}
}
return nil
}
// GetAllItemsFromID gets all items with a specific ID
func (pil *PlayerItemList) GetAllItemsFromID(itemID int32, includeBank bool, lock bool) []*Item {
if lock {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
}
var matchingItems []*Item
for bagID, bagMap := range pil.items {
// Skip bank slots if not including bank
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
continue
}
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil && item.Details.ItemID == itemID {
matchingItems = append(matchingItems, item)
}
}
}
}
return matchingItems
}
// RemoveItem removes an item from inventory
func (pil *PlayerItemList) RemoveItem(item *Item, deleteItem bool, lock bool) {
if item == nil {
return
}
if lock {
pil.mutex.Lock()
defer pil.mutex.Unlock()
}
pil.eraseItemInternal(item)
if deleteItem {
// Mark item for deletion
item.NeedsDeletion = true
}
}
// AddItem adds an item to the inventory
func (pil *PlayerItemList) AddItem(item *Item) bool {
if item == nil {
return false
}
pil.mutex.Lock()
defer pil.mutex.Unlock()
// Try to stack with existing items first
stackableItem := pil.CanStack(item, false)
if stackableItem != nil {
// Stack with existing item
stackableItem.Details.Count += item.Details.Count
if stackableItem.Details.Count > stackableItem.StackCount {
// Handle overflow
overflow := stackableItem.Details.Count - stackableItem.StackCount
stackableItem.Details.Count = stackableItem.StackCount
item.Details.Count = overflow
// Continue to add the overflow as a new item
} else {
return true // Successfully stacked
}
}
// Try to assign to free slot
var bagID int32
var slot int16
if pil.getFirstFreeSlotInternal(&bagID, &slot, true) {
item.Details.BagID = bagID
item.Details.SlotID = slot
item.Details.AppearanceType = BaseEquipment
pil.addItemToLocationInternal(item, bagID, BaseEquipment, slot)
return true
}
// Add to overflow if no free slots
return pil.AddOverflowItem(item)
}
// GetItem gets an item from a specific location
func (pil *PlayerItemList) GetItem(bagSlot int32, slot int16, appearanceType int8) *Item {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
if bagMap, exists := pil.items[bagSlot]; exists {
if slotMap, exists := bagMap[appearanceType]; exists {
if item, exists := slotMap[slot]; exists {
return item
}
}
}
return nil
}
// GetAllItems returns all items in the inventory
func (pil *PlayerItemList) GetAllItems() map[int32]*Item {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
// Return a copy of indexed items
allItems := make(map[int32]*Item)
for index, item := range pil.indexedItems {
allItems[index] = item
}
return allItems
}
// HasFreeBankSlot checks if there's a free bank slot
func (pil *PlayerItemList) HasFreeBankSlot() bool {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
// Check bank bag slots
for bagSlot := int32(BankSlot1); bagSlot <= BankSlot8; bagSlot++ {
if _, exists := pil.items[bagSlot]; !exists {
return true
}
}
return false
}
// FindFreeBankSlot finds the first free bank slot
func (pil *PlayerItemList) FindFreeBankSlot() int8 {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
for bagSlot := int32(BankSlot1); bagSlot <= BankSlot8; bagSlot++ {
if _, exists := pil.items[bagSlot]; !exists {
return int8(bagSlot - BankSlot1)
}
}
return -1
}
// GetFirstFreeSlot gets the first free slot coordinates
func (pil *PlayerItemList) GetFirstFreeSlot(bagID *int32, slot *int16) bool {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
return pil.getFirstFreeSlotInternal(bagID, slot, true)
}
// getFirstFreeSlotInternal gets the first free slot (assumes lock is held)
func (pil *PlayerItemList) getFirstFreeSlotInternal(bagID *int32, slot *int16, inventoryOnly bool) bool {
// Check main inventory bags first
for bagSlotID := int32(0); bagSlotID < NumInvSlots; bagSlotID++ {
bag := pil.GetBag(int8(bagSlotID), false)
if bag != nil && bag.BagInfo != nil {
// Check slots in this bag
bagMap := pil.items[bagSlotID]
if bagMap == nil {
bagMap = make(map[int8]map[int16]*Item)
pil.items[bagSlotID] = bagMap
}
slotMap := bagMap[BaseEquipment]
if slotMap == nil {
slotMap = make(map[int16]*Item)
bagMap[BaseEquipment] = slotMap
}
for slotID := int16(0); slotID < int16(bag.BagInfo.NumSlots); slotID++ {
if _, exists := slotMap[slotID]; !exists {
*bagID = bagSlotID
*slot = slotID
return true
}
}
}
}
// Check bank bags if not inventory only
if !inventoryOnly {
for bagSlotID := int32(BankSlot1); bagSlotID <= BankSlot8; bagSlotID++ {
bag := pil.GetBankBag(int8(bagSlotID-BankSlot1), false)
if bag != nil && bag.BagInfo != nil {
bagMap := pil.items[bagSlotID]
if bagMap == nil {
bagMap = make(map[int8]map[int16]*Item)
pil.items[bagSlotID] = bagMap
}
slotMap := bagMap[BaseEquipment]
if slotMap == nil {
slotMap = make(map[int16]*Item)
bagMap[BaseEquipment] = slotMap
}
for slotID := int16(0); slotID < int16(bag.BagInfo.NumSlots); slotID++ {
if _, exists := slotMap[slotID]; !exists {
*bagID = bagSlotID
*slot = slotID
return true
}
}
}
}
}
return false
}
// GetFirstFreeBankSlot gets the first free bank slot coordinates
func (pil *PlayerItemList) GetFirstFreeBankSlot(bagID *int32, slot *int16) bool {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
return pil.getFirstFreeSlotInternal(bagID, slot, false)
}
// GetBankBag gets a bank bag by slot
func (pil *PlayerItemList) GetBankBag(inventorySlot int8, lock bool) *Item {
if lock {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
}
bagID := int32(BankSlot1) + int32(inventorySlot)
if bagMap, exists := pil.items[bagID]; exists {
if slotMap, exists := bagMap[0]; exists {
if item, exists := slotMap[0]; exists && item != nil && item.IsBag() {
return item
}
}
}
return nil
}
// AddOverflowItem adds an item to overflow storage
func (pil *PlayerItemList) AddOverflowItem(item *Item) bool {
if item == nil {
return false
}
pil.mutex.Lock()
defer pil.mutex.Unlock()
pil.overflowItems = append(pil.overflowItems, item)
return true
}
// GetOverflowItem gets the first overflow item
func (pil *PlayerItemList) GetOverflowItem() *Item {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
if len(pil.overflowItems) > 0 {
return pil.overflowItems[0]
}
return nil
}
// RemoveOverflowItem removes an item from overflow storage
func (pil *PlayerItemList) RemoveOverflowItem(item *Item) {
if item == nil {
return
}
pil.mutex.Lock()
defer pil.mutex.Unlock()
for i, overflowItem := range pil.overflowItems {
if overflowItem == item {
pil.overflowItems = append(pil.overflowItems[:i], pil.overflowItems[i+1:]...)
break
}
}
}
// GetOverflowItemList returns all overflow items
func (pil *PlayerItemList) GetOverflowItemList() []*Item {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
// Return a copy of the overflow list
overflowCopy := make([]*Item, len(pil.overflowItems))
copy(overflowCopy, pil.overflowItems)
return overflowCopy
}
// ResetPackets resets packet data
func (pil *PlayerItemList) ResetPackets() {
pil.mutex.Lock()
defer pil.mutex.Unlock()
pil.xorPacket = nil
pil.origPacket = nil
pil.packetCount = 0
}
// CheckSlotConflict checks for slot conflicts (lore items, etc.)
func (pil *PlayerItemList) CheckSlotConflict(item *Item, checkLoreOnly bool, lockMutex bool, loreStackCount *int16) int32 {
if item == nil {
return 0
}
if lockMutex {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
}
// Check for lore conflicts
if item.CheckFlag(Lore) || item.CheckFlag(LoreEquip) {
stackCount := int16(0)
for _, bagMap := range pil.items {
for _, slotMap := range bagMap {
for _, existingItem := range slotMap {
if existingItem != nil && existingItem.Details.ItemID == item.Details.ItemID {
stackCount++
}
}
}
}
if loreStackCount != nil {
*loreStackCount = stackCount
}
if stackCount > 0 {
return 1 // Lore conflict
}
}
return 0 // No conflict
}
// GetItemCountInBag returns the number of items in a bag
func (pil *PlayerItemList) GetItemCountInBag(bag *Item) int32 {
if bag == nil || !bag.IsBag() {
return 0
}
pil.mutex.RLock()
defer pil.mutex.RUnlock()
count := int32(0)
if bagMap, exists := pil.items[bag.Details.BagID]; exists {
for _, slotMap := range bagMap {
count += int32(len(slotMap))
}
}
return count
}
// GetFirstNewItem gets the index of the first new item
func (pil *PlayerItemList) GetFirstNewItem() int16 {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
for _, bagMap := range pil.items {
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil && item.Details.NewItem {
return item.Details.NewIndex
}
}
}
}
return -1
}
// GetNewItemByIndex gets a new item by its index
func (pil *PlayerItemList) GetNewItemByIndex(index int16) int16 {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
for _, bagMap := range pil.items {
for _, slotMap := range bagMap {
for _, item := range slotMap {
if item != nil && item.Details.NewItem && item.Details.NewIndex == index {
return index
}
}
}
}
return -1
}
// addItemToLocationInternal adds an item to a specific location (assumes lock is held)
func (pil *PlayerItemList) addItemToLocationInternal(item *Item, bagID int32, appearanceType int8, slot int16) {
if item == nil {
return
}
// Ensure bag map exists
if pil.items[bagID] == nil {
pil.items[bagID] = make(map[int8]map[int16]*Item)
}
// Ensure appearance type map exists
if pil.items[bagID][appearanceType] == nil {
pil.items[bagID][appearanceType] = make(map[int16]*Item)
}
// Add item to location
pil.items[bagID][appearanceType][slot] = item
// Add to indexed items
if item.Details.Index > 0 {
pil.indexedItems[int32(item.Details.Index)] = item
}
}
// IsItemInSlotType checks if an item is in a specific slot type
func (pil *PlayerItemList) IsItemInSlotType(item *Item, slotType InventorySlotType, lockItems bool) bool {
if item == nil {
return false
}
if lockItems {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
}
bagID := item.Details.BagID
switch slotType {
case BaseInventory:
return bagID >= 0 && bagID < NumInvSlots
case Bank:
return bagID >= BankSlot1 && bagID <= BankSlot8
case SharedBank:
// TODO: Implement shared bank slot detection
return false
case Overflow:
// Check if item is in overflow list
for _, overflowItem := range pil.overflowItems {
if overflowItem == item {
return true
}
}
return false
}
return false
}
// String returns a string representation of the player item list
func (pil *PlayerItemList) String() string {
pil.mutex.RLock()
defer pil.mutex.RUnlock()
return fmt.Sprintf("PlayerItemList{Items: %d, Overflow: %d, MaxIndex: %d}",
len(pil.indexedItems), len(pil.overflowItems), pil.maxSavedIndex)
}
func init() {
log.Printf("Player item list system initialized")
}

561
internal/items/types.go Normal file
View File

@ -0,0 +1,561 @@
package items
import (
"sync"
"time"
)
// Item effect types
type ItemEffectType int
const (
NoEffectType ItemEffectType = 0
EffectCureTypeTrauma ItemEffectType = 1
EffectCureTypeArcane ItemEffectType = 2
EffectCureTypeNoxious ItemEffectType = 3
EffectCureTypeElemental ItemEffectType = 4
EffectCureTypeCurse ItemEffectType = 5
EffectCureTypeMagic ItemEffectType = 6
EffectCureTypeAll ItemEffectType = 7
)
// Inventory slot types
type InventorySlotType int
const (
HouseVault InventorySlotType = -5
SharedBank InventorySlotType = -4
Bank InventorySlotType = -3
Overflow InventorySlotType = -2
UnknownInvSlotType InventorySlotType = -1
BaseInventory InventorySlotType = 0
)
// Lock reasons for items
type LockReason uint32
const (
LockReasonNone LockReason = 0
LockReasonHouse LockReason = 1 << 0
LockReasonCrafting LockReason = 1 << 1
LockReasonShop LockReason = 1 << 2
)
// Add item types for tracking how items were added
type AddItemType int
const (
NotSet AddItemType = 0
BuyFromBroker AddItemType = 1
GMCommand AddItemType = 2
)
// ItemStatsValues represents the complete stat bonuses from an item
type ItemStatsValues struct {
Str int16 `json:"str"`
Sta int16 `json:"sta"`
Agi int16 `json:"agi"`
Wis int16 `json:"wis"`
Int int16 `json:"int"`
VsSlash int16 `json:"vs_slash"`
VsCrush int16 `json:"vs_crush"`
VsPierce int16 `json:"vs_pierce"`
VsPhysical int16 `json:"vs_physical"`
VsHeat int16 `json:"vs_heat"`
VsCold int16 `json:"vs_cold"`
VsMagic int16 `json:"vs_magic"`
VsMental int16 `json:"vs_mental"`
VsDivine int16 `json:"vs_divine"`
VsDisease int16 `json:"vs_disease"`
VsPoison int16 `json:"vs_poison"`
Health int16 `json:"health"`
Power int16 `json:"power"`
Concentration int8 `json:"concentration"`
AbilityModifier int16 `json:"ability_modifier"`
CriticalMitigation int16 `json:"critical_mitigation"`
ExtraShieldBlockChance int16 `json:"extra_shield_block_chance"`
BeneficialCritChance int16 `json:"beneficial_crit_chance"`
CritBonus int16 `json:"crit_bonus"`
Potency int16 `json:"potency"`
HateGainMod int16 `json:"hate_gain_mod"`
AbilityReuseSpeed int16 `json:"ability_reuse_speed"`
AbilityCastingSpeed int16 `json:"ability_casting_speed"`
AbilityRecoverySpeed int16 `json:"ability_recovery_speed"`
SpellReuseSpeed int16 `json:"spell_reuse_speed"`
SpellMultiAttackChance int16 `json:"spell_multi_attack_chance"`
DPS int16 `json:"dps"`
AttackSpeed int16 `json:"attack_speed"`
MultiAttackChance int16 `json:"multi_attack_chance"`
Flurry int16 `json:"flurry"`
AEAutoattackChance int16 `json:"ae_autoattack_chance"`
Strikethrough int16 `json:"strikethrough"`
Accuracy int16 `json:"accuracy"`
OffensiveSpeed int16 `json:"offensive_speed"`
UncontestedParry float32 `json:"uncontested_parry"`
UncontestedBlock float32 `json:"uncontested_block"`
UncontestedDodge float32 `json:"uncontested_dodge"`
UncontestedRiposte float32 `json:"uncontested_riposte"`
SizeMod float32 `json:"size_mod"`
}
// ItemCore contains the core data for an item instance
type ItemCore struct {
ItemID int32 `json:"item_id"`
SOEId int32 `json:"soe_id"`
BagID int32 `json:"bag_id"`
InvSlotID int32 `json:"inv_slot_id"`
SlotID int16 `json:"slot_id"`
EquipSlotID int16 `json:"equip_slot_id"` // used when a bag is equipped
AppearanceType int16 `json:"appearance_type"` // 0 for combat armor, 1 for appearance armor
Index int8 `json:"index"`
Icon int16 `json:"icon"`
ClassicIcon int16 `json:"classic_icon"`
Count int16 `json:"count"`
Tier int8 `json:"tier"`
NumSlots int8 `json:"num_slots"`
UniqueID int64 `json:"unique_id"`
NumFreeSlots int8 `json:"num_free_slots"`
RecommendedLevel int16 `json:"recommended_level"`
ItemLocked bool `json:"item_locked"`
LockFlags int32 `json:"lock_flags"`
NewItem bool `json:"new_item"`
NewIndex int16 `json:"new_index"`
}
// ItemStat represents a single stat on an item
type ItemStat struct {
StatName string `json:"stat_name"`
StatType int32 `json:"stat_type"`
StatSubtype int16 `json:"stat_subtype"`
StatTypeCombined int16 `json:"stat_type_combined"`
Value float32 `json:"value"`
Level int8 `json:"level"`
}
// ItemSet represents an item set piece
type ItemSet struct {
ItemID int32 `json:"item_id"`
ItemCRC int32 `json:"item_crc"`
ItemIcon int16 `json:"item_icon"`
ItemStackSize int16 `json:"item_stack_size"`
ItemListColor int32 `json:"item_list_color"`
Name string `json:"name"`
Language int8 `json:"language"`
}
// Classifications represents item classifications
type Classifications struct {
ClassificationID int32 `json:"classification_id"`
ClassificationName string `json:"classification_name"`
}
// ItemLevelOverride represents level overrides for specific classes
type ItemLevelOverride struct {
AdventureClass int8 `json:"adventure_class"`
TradeskillClass int8 `json:"tradeskill_class"`
Level int16 `json:"level"`
}
// ItemClass represents class requirements for an item
type ItemClass struct {
AdventureClass int8 `json:"adventure_class"`
TradeskillClass int8 `json:"tradeskill_class"`
Level int16 `json:"level"`
}
// ItemAppearance represents visual appearance data
type ItemAppearance struct {
Type int16 `json:"type"`
Red int8 `json:"red"`
Green int8 `json:"green"`
Blue int8 `json:"blue"`
HighlightRed int8 `json:"highlight_red"`
HighlightGreen int8 `json:"highlight_green"`
HighlightBlue int8 `json:"highlight_blue"`
}
// QuestRewardData represents quest reward information
type QuestRewardData struct {
QuestID int32 `json:"quest_id"`
IsTemporary bool `json:"is_temporary"`
Description string `json:"description"`
IsCollection bool `json:"is_collection"`
HasDisplayed bool `json:"has_displayed"`
TmpCoin int64 `json:"tmp_coin"`
TmpStatus int32 `json:"tmp_status"`
DbSaved bool `json:"db_saved"`
DbIndex int32 `json:"db_index"`
}
// Generic_Info contains general item information
type GenericInfo struct {
ShowName int8 `json:"show_name"`
CreatorFlag int8 `json:"creator_flag"`
ItemFlags int16 `json:"item_flags"`
ItemFlags2 int16 `json:"item_flags2"`
Condition int8 `json:"condition"`
Weight int32 `json:"weight"` // num/10
SkillReq1 int32 `json:"skill_req1"`
SkillReq2 int32 `json:"skill_req2"`
SkillMin int16 `json:"skill_min"`
ItemType int8 `json:"item_type"`
AppearanceID int16 `json:"appearance_id"`
AppearanceRed int8 `json:"appearance_red"`
AppearanceGreen int8 `json:"appearance_green"`
AppearanceBlue int8 `json:"appearance_blue"`
AppearanceHighlightRed int8 `json:"appearance_highlight_red"`
AppearanceHighlightGreen int8 `json:"appearance_highlight_green"`
AppearanceHighlightBlue int8 `json:"appearance_highlight_blue"`
Collectable int8 `json:"collectable"`
OffersQuestID int32 `json:"offers_quest_id"`
PartOfQuestID int32 `json:"part_of_quest_id"`
MaxCharges int16 `json:"max_charges"`
DisplayCharges int8 `json:"display_charges"`
AdventureClasses int64 `json:"adventure_classes"`
TradeskillClasses int64 `json:"tradeskill_classes"`
AdventureDefaultLevel int16 `json:"adventure_default_level"`
TradeskillDefaultLevel int16 `json:"tradeskill_default_level"`
Usable int8 `json:"usable"`
Harvest int8 `json:"harvest"`
BodyDrop int8 `json:"body_drop"`
PvPDescription int8 `json:"pvp_description"`
MercOnly int8 `json:"merc_only"`
MountOnly int8 `json:"mount_only"`
SetID int32 `json:"set_id"`
CollectableUnk int8 `json:"collectable_unk"`
OffersQuestName string `json:"offers_quest_name"`
RequiredByQuestName string `json:"required_by_quest_name"`
TransmutedMaterial int8 `json:"transmuted_material"`
}
// ArmorInfo contains armor-specific information
type ArmorInfo struct {
MitigationLow int16 `json:"mitigation_low"`
MitigationHigh int16 `json:"mitigation_high"`
}
// AdornmentInfo contains adornment-specific information
type AdornmentInfo struct {
Duration float32 `json:"duration"`
ItemTypes int16 `json:"item_types"`
SlotType int16 `json:"slot_type"`
}
// WeaponInfo contains weapon-specific information
type WeaponInfo struct {
WieldType int16 `json:"wield_type"`
DamageLow1 int16 `json:"damage_low1"`
DamageHigh1 int16 `json:"damage_high1"`
DamageLow2 int16 `json:"damage_low2"`
DamageHigh2 int16 `json:"damage_high2"`
DamageLow3 int16 `json:"damage_low3"`
DamageHigh3 int16 `json:"damage_high3"`
Delay int16 `json:"delay"`
Rating float32 `json:"rating"`
}
// ShieldInfo contains shield-specific information
type ShieldInfo struct {
ArmorInfo ArmorInfo `json:"armor_info"`
}
// RangedInfo contains ranged weapon information
type RangedInfo struct {
WeaponInfo WeaponInfo `json:"weapon_info"`
RangeLow int16 `json:"range_low"`
RangeHigh int16 `json:"range_high"`
}
// BagInfo contains bag-specific information
type BagInfo struct {
NumSlots int8 `json:"num_slots"`
WeightReduction int16 `json:"weight_reduction"`
}
// FoodInfo contains food/drink information
type FoodInfo struct {
Type int8 `json:"type"` // 0=water, 1=food
Level int8 `json:"level"`
Duration float32 `json:"duration"`
Satiation int8 `json:"satiation"`
}
// BaubleInfo contains bauble-specific information
type BaubleInfo struct {
Cast int16 `json:"cast"`
Recovery int16 `json:"recovery"`
Duration int32 `json:"duration"`
Recast float32 `json:"recast"`
DisplaySlotOptional int8 `json:"display_slot_optional"`
DisplayCastTime int8 `json:"display_cast_time"`
DisplayBaubleType int8 `json:"display_bauble_type"`
EffectRadius float32 `json:"effect_radius"`
MaxAOETargets int32 `json:"max_aoe_targets"`
DisplayUntilCancelled int8 `json:"display_until_cancelled"`
}
// BookInfo contains book-specific information
type BookInfo struct {
Language int8 `json:"language"`
Author string `json:"author"`
Title string `json:"title"`
}
// BookInfoPages represents a book page
type BookInfoPages struct {
Page int8 `json:"page"`
PageText string `json:"page_text"`
PageTextVAlign int8 `json:"page_text_valign"`
PageTextHAlign int8 `json:"page_text_halign"`
}
// SkillInfo contains skill book information
type SkillInfo struct {
SpellID int32 `json:"spell_id"`
SpellTier int32 `json:"spell_tier"`
}
// HouseItemInfo contains house item information
type HouseItemInfo struct {
StatusRentReduction int32 `json:"status_rent_reduction"`
CoinRentReduction float32 `json:"coin_rent_reduction"`
HouseOnly int8 `json:"house_only"`
HouseLocation int8 `json:"house_location"` // 0 = floor, 1 = ceiling, 2 = wall
}
// HouseContainerInfo contains house container information
type HouseContainerInfo struct {
AllowedTypes int64 `json:"allowed_types"`
NumSlots int8 `json:"num_slots"`
BrokerCommission int8 `json:"broker_commission"`
FenceCommission int8 `json:"fence_commission"`
}
// RecipeBookInfo contains recipe book information
type RecipeBookInfo struct {
Recipes []uint32 `json:"recipes"`
RecipeID int32 `json:"recipe_id"`
Uses int8 `json:"uses"`
}
// ItemSetInfo contains item set information
type ItemSetInfo struct {
ItemID int32 `json:"item_id"`
ItemCRC int32 `json:"item_crc"`
ItemIcon int16 `json:"item_icon"`
ItemStackSize int32 `json:"item_stack_size"`
ItemListColor int32 `json:"item_list_color"`
SOEItemIDUnsigned int32 `json:"soe_item_id_unsigned"`
SOEItemCRCUnsigned int32 `json:"soe_item_crc_unsigned"`
}
// ThrownInfo contains thrown weapon information
type ThrownInfo struct {
Range int32 `json:"range"`
DamageModifier int32 `json:"damage_modifier"`
HitBonus float32 `json:"hit_bonus"`
DamageType int32 `json:"damage_type"`
}
// ItemEffect represents an item effect
type ItemEffect struct {
Effect string `json:"effect"`
Percentage int8 `json:"percentage"`
SubBulletFlag int8 `json:"sub_bullet_flag"`
}
// BookPage represents a book page
type BookPage struct {
Page int8 `json:"page"`
PageText string `json:"page_text"`
VAlign int8 `json:"valign"`
HAlign int8 `json:"halign"`
}
// ItemStatString represents a string-based item stat
type ItemStatString struct {
StatString string `json:"stat_string"`
}
// Item represents a complete item with all its properties
type Item struct {
// Basic item information
LowerName string `json:"lower_name"`
Name string `json:"name"`
Description string `json:"description"`
StackCount int16 `json:"stack_count"`
SellPrice int32 `json:"sell_price"`
SellStatus int32 `json:"sell_status"`
MaxSellValue int32 `json:"max_sell_value"`
BrokerPrice int64 `json:"broker_price"`
// Search and state flags
IsSearchStoreItem bool `json:"is_search_store_item"`
IsSearchInInventory bool `json:"is_search_in_inventory"`
SaveNeeded bool `json:"save_needed"`
NoBuyBack bool `json:"no_buy_back"`
NoSale bool `json:"no_sale"`
NeedsDeletion bool `json:"needs_deletion"`
Crafted bool `json:"crafted"`
Tinkered bool `json:"tinkered"`
// Item metadata
WeaponType int8 `json:"weapon_type"`
Adornment string `json:"adornment"`
Creator string `json:"creator"`
SellerName string `json:"seller_name"`
SellerCharID int32 `json:"seller_char_id"`
SellerHouseID int64 `json:"seller_house_id"`
Created time.Time `json:"created"`
GroupedCharIDs map[int32]bool `json:"grouped_char_ids"`
EffectType ItemEffectType `json:"effect_type"`
BookLanguage int8 `json:"book_language"`
// Adornment slots
Adorn0 int32 `json:"adorn0"`
Adorn1 int32 `json:"adorn1"`
Adorn2 int32 `json:"adorn2"`
// Spell information
SpellID int32 `json:"spell_id"`
SpellTier int8 `json:"spell_tier"`
ItemScript string `json:"item_script"`
// Collections and arrays
Classifications []*Classifications `json:"classifications"`
ItemStats []*ItemStat `json:"item_stats"`
ItemSets []*ItemSet `json:"item_sets"`
ItemStringStats []*ItemStatString `json:"item_string_stats"`
ItemLevelOverrides []*ItemLevelOverride `json:"item_level_overrides"`
ItemEffects []*ItemEffect `json:"item_effects"`
BookPages []*BookPage `json:"book_pages"`
SlotData []int8 `json:"slot_data"`
// Core item data
Details ItemCore `json:"details"`
GenericInfo GenericInfo `json:"generic_info"`
// Type-specific information (pointers to allow nil for unused types)
WeaponInfo *WeaponInfo `json:"weapon_info,omitempty"`
RangedInfo *RangedInfo `json:"ranged_info,omitempty"`
ArmorInfo *ArmorInfo `json:"armor_info,omitempty"`
AdornmentInfo *AdornmentInfo `json:"adornment_info,omitempty"`
BagInfo *BagInfo `json:"bag_info,omitempty"`
FoodInfo *FoodInfo `json:"food_info,omitempty"`
BaubleInfo *BaubleInfo `json:"bauble_info,omitempty"`
BookInfo *BookInfo `json:"book_info,omitempty"`
BookInfoPages *BookInfoPages `json:"book_info_pages,omitempty"`
HouseItemInfo *HouseItemInfo `json:"house_item_info,omitempty"`
HouseContainerInfo *HouseContainerInfo `json:"house_container_info,omitempty"`
SkillInfo *SkillInfo `json:"skill_info,omitempty"`
RecipeBookInfo *RecipeBookInfo `json:"recipe_book_info,omitempty"`
ItemSetInfo *ItemSetInfo `json:"item_set_info,omitempty"`
ThrownInfo *ThrownInfo `json:"thrown_info,omitempty"`
// Thread safety
mutex sync.RWMutex
}
// MasterItemList manages all items in the game
type MasterItemList struct {
items map[int32]*Item `json:"items"`
mappedItemStatsStrings map[string]int32 `json:"mapped_item_stats_strings"`
mappedItemStatTypeIDs map[int32]string `json:"mapped_item_stat_type_ids"`
brokerItemMap map[*VersionRange]map[int64]int64 `json:"-"` // Complex type, exclude from JSON
mutex sync.RWMutex
}
// VersionRange represents a version range for broker item mapping
type VersionRange struct {
MinVersion int32 `json:"min_version"`
MaxVersion int32 `json:"max_version"`
}
// PlayerItemList manages a player's inventory
type PlayerItemList struct {
maxSavedIndex int32 `json:"max_saved_index"`
indexedItems map[int32]*Item `json:"indexed_items"`
items map[int32]map[int8]map[int16]*Item `json:"items"`
overflowItems []*Item `json:"overflow_items"`
packetCount int16 `json:"packet_count"`
xorPacket []byte `json:"-"` // Exclude from JSON
origPacket []byte `json:"-"` // Exclude from JSON
mutex sync.RWMutex
}
// EquipmentItemList manages equipped items for a character
type EquipmentItemList struct {
items [NumSlots]*Item `json:"items"`
appearanceType int8 `json:"appearance_type"` // 0 for normal equip, 1 for appearance
xorPacket []byte `json:"-"` // Exclude from JSON
origPacket []byte `json:"-"` // Exclude from JSON
mutex sync.RWMutex
}
// ItemManagerStats represents statistics about item management
type ItemManagerStats struct {
TotalItems int32 `json:"total_items"`
ItemsByType map[int8]int32 `json:"items_by_type"`
ItemsByTier map[int8]int32 `json:"items_by_tier"`
PlayersWithItems int32 `json:"players_with_items"`
TotalItemInstances int64 `json:"total_item_instances"`
AverageItemsPerPlayer float32 `json:"average_items_per_player"`
LastUpdate time.Time `json:"last_update"`
}
// ItemSearchCriteria represents search criteria for items
type ItemSearchCriteria struct {
Name string `json:"name"`
ItemType int64 `json:"item_type"`
LocationType int64 `json:"location_type"`
BrokerType int64 `json:"broker_type"`
MinPrice int64 `json:"min_price"`
MaxPrice int64 `json:"max_price"`
MinSkill int8 `json:"min_skill"`
MaxSkill int8 `json:"max_skill"`
Seller string `json:"seller"`
Adornment string `json:"adornment"`
MinTier int8 `json:"min_tier"`
MaxTier int8 `json:"max_tier"`
MinLevel int16 `json:"min_level"`
MaxLevel int16 `json:"max_level"`
ItemClass int8 `json:"item_class"`
AdditionalCriteria map[string]string `json:"additional_criteria"`
}
// ItemValidationResult represents the result of item validation
type ItemValidationResult struct {
Valid bool `json:"valid"`
Errors []string `json:"errors,omitempty"`
}
// ItemError represents an item-specific error
type ItemError struct {
message string
}
func (e *ItemError) Error() string {
return e.message
}
// NewItemError creates a new item error
func NewItemError(message string) *ItemError {
return &ItemError{message: message}
}
// IsItemError checks if an error is an ItemError
func IsItemError(err error) bool {
_, ok := err.(*ItemError)
return ok
}
// Common item errors
var (
ErrItemNotFound = NewItemError("item not found")
ErrInvalidItem = NewItemError("invalid item")
ErrItemLocked = NewItemError("item is locked")
ErrInsufficientSpace = NewItemError("insufficient inventory space")
ErrCannotEquip = NewItemError("cannot equip item")
ErrCannotTrade = NewItemError("cannot trade item")
ErrItemExpired = NewItemError("item has expired")
)

View File

@ -6,18 +6,18 @@ const (
MaxLanguageNameLength = 50
// Special language IDs (common in EQ2)
LanguageIDCommon = 0 // Common tongue (default)
LanguageIDElvish = 1 // Elvish
LanguageIDDwarven = 2 // Dwarven
LanguageIDHalfling = 3 // Halfling
LanguageIDGnomish = 4 // Gnomish
LanguageIDIksar = 5 // Iksar
LanguageIDTrollish = 6 // Trollish
LanguageIDOgrish = 7 // Ogrish
LanguageIDFae = 8 // Fae
LanguageIDArasai = 9 // Arasai
LanguageIDSarnak = 10 // Sarnak
LanguageIDFroglok = 11 // Froglok
LanguageIDCommon = 0 // Common tongue (default)
LanguageIDElvish = 1 // Elvish
LanguageIDDwarven = 2 // Dwarven
LanguageIDHalfling = 3 // Halfling
LanguageIDGnomish = 4 // Gnomish
LanguageIDIksar = 5 // Iksar
LanguageIDTrollish = 6 // Trollish
LanguageIDOgrish = 7 // Ogrish
LanguageIDFae = 8 // Fae
LanguageIDArasai = 9 // Arasai
LanguageIDSarnak = 10 // Sarnak
LanguageIDFroglok = 11 // Froglok
)
// Language validation constants
@ -34,6 +34,6 @@ const (
// System limits
const (
MaxLanguagesPerPlayer = 100 // Reasonable limit to prevent abuse
MaxLanguagesPerPlayer = 100 // Reasonable limit to prevent abuse
MaxTotalLanguages = 1000 // System-wide language limit
)

View File

@ -1,5 +1,7 @@
package languages
import "fmt"
// Database interface for language persistence
type Database interface {
LoadAllLanguages() ([]*Language, error)
@ -69,11 +71,11 @@ type ChatProcessor interface {
// PlayerLanguageAdapter provides language functionality for players
type PlayerLanguageAdapter struct {
player *Player
languages *PlayerLanguagesList
primaryLang int32
manager *Manager
logger Logger
player *Player
languages *PlayerLanguagesList
primaryLang int32
manager *Manager
logger Logger
}
// NewPlayerLanguageAdapter creates a new player language adapter

View File

@ -7,24 +7,24 @@ import (
// Manager provides high-level management of the language system
type Manager struct {
masterLanguagesList *MasterLanguagesList
database Database
logger Logger
mutex sync.RWMutex
masterLanguagesList *MasterLanguagesList
database Database
logger Logger
mutex sync.RWMutex
// Statistics
languageLookups int64
playersWithLanguages int64
languageUsageCount map[int32]int64 // Language ID -> usage count
languageLookups int64
playersWithLanguages int64
languageUsageCount map[int32]int64 // Language ID -> usage count
}
// NewManager creates a new language manager
func NewManager(database Database, logger Logger) *Manager {
return &Manager{
masterLanguagesList: NewMasterLanguagesList(),
database: database,
logger: logger,
languageUsageCount: make(map[int32]int64),
masterLanguagesList: NewMasterLanguagesList(),
database: database,
logger: logger,
languageUsageCount: make(map[int32]int64),
}
}
@ -199,11 +199,11 @@ func (m *Manager) GetStatistics() *LanguageStatistics {
}
return &LanguageStatistics{
TotalLanguages: len(allLanguages),
PlayersWithLanguages: int(m.playersWithLanguages),
LanguageUsageCount: usageCount,
LanguageLookups: m.languageLookups,
LanguagesByName: languagesByName,
TotalLanguages: len(allLanguages),
PlayersWithLanguages: int(m.playersWithLanguages),
LanguageUsageCount: usageCount,
LanguageLookups: m.languageLookups,
LanguagesByName: languagesByName,
}
}

View File

@ -7,9 +7,9 @@ import (
// Language represents a single language that can be learned by players
type Language struct {
id int32 // Unique language identifier
name string // Language name
saveNeeded bool // Whether this language needs to be saved to database
id int32 // Unique language identifier
name string // Language name
saveNeeded bool // Whether this language needs to be saved to database
mutex sync.RWMutex // Thread safety
}
@ -122,7 +122,7 @@ func (l *Language) Copy() *Language {
// MasterLanguagesList manages the global list of all available languages
type MasterLanguagesList struct {
languages map[int32]*Language // Languages indexed by ID for fast lookup
languages map[int32]*Language // Languages indexed by ID for fast lookup
nameIndex map[string]*Language // Languages indexed by name for name lookups
mutex sync.RWMutex // Thread safety
}
@ -301,7 +301,7 @@ func (mll *MasterLanguagesList) GetLanguageNames() []string {
// PlayerLanguagesList manages languages known by a specific player
type PlayerLanguagesList struct {
languages map[int32]*Language // Player's languages indexed by ID
languages map[int32]*Language // Player's languages indexed by ID
nameIndex map[string]*Language // Player's languages indexed by name
mutex sync.RWMutex // Thread safety
}
@ -452,9 +452,9 @@ func (pll *PlayerLanguagesList) GetLanguageNames() []string {
// LanguageStatistics contains language system statistics
type LanguageStatistics struct {
TotalLanguages int `json:"total_languages"`
PlayersWithLanguages int `json:"players_with_languages"`
LanguageUsageCount map[int32]int64 `json:"language_usage_count"`
LanguageLookups int64 `json:"language_lookups"`
LanguagesByName map[string]int32 `json:"languages_by_name"`
TotalLanguages int `json:"total_languages"`
PlayersWithLanguages int `json:"players_with_languages"`
LanguageUsageCount map[int32]int64 `json:"language_usage_count"`
LanguageLookups int64 `json:"language_lookups"`
LanguagesByName map[string]int32 `json:"languages_by_name"`
}

View File

@ -60,14 +60,14 @@ type Brain interface {
// BaseBrain provides the default AI implementation
type BaseBrain struct {
npc NPC // The NPC this brain controls
brainType int8 // Type of brain
state *BrainState // Brain state management
hateList *HateList // Hate management
encounterList *EncounterList // Encounter management
statistics *BrainStatistics // Performance statistics
logger Logger // Logger interface
mutex sync.RWMutex // Thread safety
npc NPC // The NPC this brain controls
brainType int8 // Type of brain
state *BrainState // Brain state management
hateList *HateList // Hate management
encounterList *EncounterList // Encounter management
statistics *BrainStatistics // Performance statistics
logger Logger // Logger interface
mutex sync.RWMutex // Thread safety
}
// NewBaseBrain creates a new base brain

View File

@ -2,36 +2,36 @@ package ai
// AI tick constants
const (
DefaultThinkTick int32 = 250 // Default think tick in milliseconds (1/4 second)
FastThinkTick int32 = 100 // Fast think tick for active AI
SlowThinkTick int32 = 1000 // Slow think tick for idle AI
BlankBrainTick int32 = 50000 // Very slow tick for blank brain
MaxThinkTick int32 = 60000 // Maximum think tick (1 minute)
DefaultThinkTick int32 = 250 // Default think tick in milliseconds (1/4 second)
FastThinkTick int32 = 100 // Fast think tick for active AI
SlowThinkTick int32 = 1000 // Slow think tick for idle AI
BlankBrainTick int32 = 50000 // Very slow tick for blank brain
MaxThinkTick int32 = 60000 // Maximum think tick (1 minute)
)
// Combat constants
const (
MaxChaseDistance float32 = 150.0 // Default max chase distance
MaxCombatRange float32 = 25.0 // Default max combat range
RunbackThreshold float32 = 1.0 // Distance threshold for runback
MaxChaseDistance float32 = 150.0 // Default max chase distance
MaxCombatRange float32 = 25.0 // Default max combat range
RunbackThreshold float32 = 1.0 // Distance threshold for runback
)
// Hate system constants
const (
MinHateValue int32 = 1 // Minimum hate value (0 or negative is invalid)
MaxHateValue int32 = 2147483647 // Maximum hate value (INT_MAX)
DefaultHateValue int32 = 100 // Default hate amount
MaxHateListSize int = 100 // Maximum entities in hate list
MinHateValue int32 = 1 // Minimum hate value (0 or negative is invalid)
MaxHateValue int32 = 2147483647 // Maximum hate value (INT_MAX)
DefaultHateValue int32 = 100 // Default hate amount
MaxHateListSize int = 100 // Maximum entities in hate list
)
// Encounter system constants
const (
MaxEncounterSize int = 50 // Maximum entities in encounter list
MaxEncounterSize int = 50 // Maximum entities in encounter list
)
// Spell recovery constants
const (
SpellRecoveryBuffer int32 = 2000 // Additional recovery time buffer (2 seconds)
SpellRecoveryBuffer int32 = 2000 // Additional recovery time buffer (2 seconds)
)
// Brain type constants for identification
@ -46,9 +46,9 @@ const (
// Pet movement constants
const (
PetMovementFollow int8 = 0
PetMovementStay int8 = 1
PetMovementGuard int8 = 2
PetMovementFollow int8 = 0
PetMovementStay int8 = 1
PetMovementGuard int8 = 2
)
// Encounter state constants
@ -60,31 +60,31 @@ const (
// Combat decision constants
const (
MeleeAttackChance int = 70 // Base chance for melee attack
SpellCastChance int = 30 // Base chance for spell casting
BuffCheckChance int = 50 // Chance to check for buffs
MeleeAttackChance int = 70 // Base chance for melee attack
SpellCastChance int = 30 // Base chance for spell casting
BuffCheckChance int = 50 // Chance to check for buffs
)
// AI state flags
const (
AIStateIdle int32 = 0
AIStateCombat int32 = 1
AIStateFollowing int32 = 2
AIStateRunback int32 = 3
AIStateCasting int32 = 4
AIStateMoving int32 = 5
AIStateIdle int32 = 0
AIStateCombat int32 = 1
AIStateFollowing int32 = 2
AIStateRunback int32 = 3
AIStateCasting int32 = 4
AIStateMoving int32 = 5
)
// Debug levels
const (
DebugLevelNone int8 = 0
DebugLevelBasic int8 = 1
DebugLevelDetailed int8 = 2
DebugLevelVerbose int8 = 3
DebugLevelNone int8 = 0
DebugLevelBasic int8 = 1
DebugLevelDetailed int8 = 2
DebugLevelVerbose int8 = 3
)
// Timer constants
const (
MillisecondsPerSecond int32 = 1000
RecoveryTimeMultiple int32 = 10 // Multiply cast/recovery times by 10
RecoveryTimeMultiple int32 = 10 // Multiply cast/recovery times by 10
)

View File

@ -1,6 +1,9 @@
package ai
import "fmt"
import (
"fmt"
"time"
)
// Logger interface for AI logging
type Logger interface {
@ -146,11 +149,11 @@ type Zone interface {
// AIManager provides high-level management of the AI system
type AIManager struct {
brains map[int32]Brain // Map of NPC ID to brain
activeCount int64 // Number of active brains
totalThinks int64 // Total think cycles processed
logger Logger // Logger for AI events
luaInterface LuaInterface // Lua script interface
brains map[int32]Brain // Map of NPC ID to brain
activeCount int64 // Number of active brains
totalThinks int64 // Total think cycles processed
logger Logger // Logger for AI events
luaInterface LuaInterface // Lua script interface
}
// NewAIManager creates a new AI manager
@ -325,10 +328,10 @@ func (am *AIManager) ClearAllBrains() {
// GetStatistics returns overall AI system statistics
func (am *AIManager) GetStatistics() *AIStatistics {
return &AIStatistics{
TotalBrains: len(am.brains),
ActiveBrains: int(am.activeCount),
TotalThinks: am.totalThinks,
BrainsByType: am.getBrainCountsByType(),
TotalBrains: len(am.brains),
ActiveBrains: int(am.activeCount),
TotalThinks: am.totalThinks,
BrainsByType: am.getBrainCountsByType(),
}
}
@ -346,10 +349,10 @@ func (am *AIManager) getBrainCountsByType() map[string]int {
// AIStatistics contains AI system statistics
type AIStatistics struct {
TotalBrains int `json:"total_brains"`
ActiveBrains int `json:"active_brains"`
TotalThinks int64 `json:"total_thinks"`
BrainsByType map[string]int `json:"brains_by_type"`
TotalBrains int `json:"total_brains"`
ActiveBrains int `json:"active_brains"`
TotalThinks int64 `json:"total_thinks"`
BrainsByType map[string]int `json:"brains_by_type"`
}
// AIBrainAdapter provides NPC functionality for brains

View File

@ -7,9 +7,9 @@ import (
// HateEntry represents a single hate entry in the hate list
type HateEntry struct {
EntityID int32 // ID of the hated entity
HateValue int32 // Amount of hate (must be >= 1)
LastUpdated int64 // Timestamp of last hate update
EntityID int32 // ID of the hated entity
HateValue int32 // Amount of hate (must be >= 1)
LastUpdated int64 // Timestamp of last hate update
}
// NewHateEntry creates a new hate entry
@ -27,8 +27,8 @@ func NewHateEntry(entityID, hateValue int32) *HateEntry {
// HateList manages the hate list for an NPC brain
type HateList struct {
entries map[int32]*HateEntry // Map of entity ID to hate entry
mutex sync.RWMutex // Thread safety
entries map[int32]*HateEntry // Map of entity ID to hate entry
mutex sync.RWMutex // Thread safety
}
// NewHateList creates a new hate list
@ -180,11 +180,11 @@ func (hl *HateList) removeOldestEntry() {
// EncounterEntry represents a single encounter participant
type EncounterEntry struct {
EntityID int32 // ID of the entity
CharacterID int32 // Character ID for players (0 for NPCs)
AddedTime int64 // When entity was added to encounter
IsPlayer bool // Whether this is a player entity
IsBot bool // Whether this is a bot entity
EntityID int32 // ID of the entity
CharacterID int32 // Character ID for players (0 for NPCs)
AddedTime int64 // When entity was added to encounter
IsPlayer bool // Whether this is a player entity
IsBot bool // Whether this is a bot entity
}
// NewEncounterEntry creates a new encounter entry
@ -200,10 +200,10 @@ func NewEncounterEntry(entityID, characterID int32, isPlayer, isBot bool) *Encou
// EncounterList manages the encounter list for an NPC brain
type EncounterList struct {
entries map[int32]*EncounterEntry // Map of entity ID to encounter entry
playerEntries map[int32]int32 // Map of character ID to entity ID
playerInEncounter bool // Whether any player is in encounter
mutex sync.RWMutex // Thread safety
entries map[int32]*EncounterEntry // Map of entity ID to encounter entry
playerEntries map[int32]int32 // Map of character ID to entity ID
playerInEncounter bool // Whether any player is in encounter
mutex sync.RWMutex // Thread safety
}
// NewEncounterList creates a new encounter list
@ -356,13 +356,13 @@ func (el *EncounterList) updatePlayerInEncounter() {
// BrainState represents the current state of a brain
type BrainState struct {
State int32 // Current AI state
LastThink int64 // Timestamp of last think cycle
ThinkTick int32 // Time between think cycles in milliseconds
SpellRecovery int64 // Timestamp when spell recovery completes
IsActive bool // Whether the brain is active
DebugLevel int8 // Debug output level
mutex sync.RWMutex
State int32 // Current AI state
LastThink int64 // Timestamp of last think cycle
ThinkTick int32 // Time between think cycles in milliseconds
SpellRecovery int64 // Timestamp when spell recovery completes
IsActive bool // Whether the brain is active
DebugLevel int8 // Debug output level
mutex sync.RWMutex
}
// NewBrainState creates a new brain state
@ -479,26 +479,26 @@ func (bs *BrainState) SetDebugLevel(level int8) {
// BrainStatistics contains brain performance statistics
type BrainStatistics struct {
ThinkCycles int64 `json:"think_cycles"`
SpellsCast int64 `json:"spells_cast"`
MeleeAttacks int64 `json:"melee_attacks"`
HateEvents int64 `json:"hate_events"`
EncounterEvents int64 `json:"encounter_events"`
AverageThinkTime float64 `json:"average_think_time_ms"`
LastThinkTime int64 `json:"last_think_time"`
TotalActiveTime int64 `json:"total_active_time_ms"`
ThinkCycles int64 `json:"think_cycles"`
SpellsCast int64 `json:"spells_cast"`
MeleeAttacks int64 `json:"melee_attacks"`
HateEvents int64 `json:"hate_events"`
EncounterEvents int64 `json:"encounter_events"`
AverageThinkTime float64 `json:"average_think_time_ms"`
LastThinkTime int64 `json:"last_think_time"`
TotalActiveTime int64 `json:"total_active_time_ms"`
}
// NewBrainStatistics creates new brain statistics
func NewBrainStatistics() *BrainStatistics {
return &BrainStatistics{
ThinkCycles: 0,
SpellsCast: 0,
MeleeAttacks: 0,
HateEvents: 0,
EncounterEvents: 0,
AverageThinkTime: 0.0,
LastThinkTime: time.Now().UnixMilli(),
TotalActiveTime: 0,
ThinkCycles: 0,
SpellsCast: 0,
MeleeAttacks: 0,
HateEvents: 0,
EncounterEvents: 0,
AverageThinkTime: 0.0,
LastThinkTime: time.Now().UnixMilli(),
TotalActiveTime: 0,
}
}

View File

@ -2,9 +2,9 @@ package npc
// AI Strategy constants
const (
AIStrategyBalanced int8 = 1
AIStrategyOffensive int8 = 2
AIStrategyDefensive int8 = 3
AIStrategyBalanced int8 = 1
AIStrategyOffensive int8 = 2
AIStrategyDefensive int8 = 3
)
// Randomize Appearances constants
@ -47,26 +47,26 @@ const (
// Cast Type constants
const (
CastOnSpawn int8 = 0
CastOnAggro int8 = 1
CastOnSpawn int8 = 0
CastOnAggro int8 = 1
MaxCastTypes int8 = 2
)
// Default values
const (
DefaultCastPercentage int8 = 25
DefaultAggroRadius float32 = 10.0
DefaultRunbackSpeed float32 = 2.0
MaxSkillBonuses int = 100
MaxNPCSpells int = 50
MaxPauseTime int32 = 300000 // 5 minutes max pause
DefaultCastPercentage int8 = 25
DefaultAggroRadius float32 = 10.0
DefaultRunbackSpeed float32 = 2.0
MaxSkillBonuses int = 100
MaxNPCSpells int = 50
MaxPauseTime int32 = 300000 // 5 minutes max pause
)
// NPC validation constants
const (
MinNPCLevel int8 = 1
MaxNPCLevel int8 = 100
MaxNPCNameLen int = 64
MinNPCLevel int8 = 1
MaxNPCLevel int8 = 100
MaxNPCNameLen int = 64
MinAppearanceID int32 = 0
MaxAppearanceID int32 = 999999
)

View File

@ -506,9 +506,9 @@ func (ca *CombatAdapter) ProcessCombat() error {
// MovementAdapter provides movement functionality for NPCs
type MovementAdapter struct {
npc *NPC
movementManager MovementManager
logger Logger
npc *NPC
movementManager MovementManager
logger Logger
}
// NewMovementAdapter creates a new movement adapter

View File

@ -10,23 +10,23 @@ import (
// Manager provides high-level management of the NPC system
type Manager struct {
npcs map[int32]*NPC // NPCs indexed by ID
npcsByZone map[int32][]*NPC // NPCs indexed by zone ID
npcsByAppearance map[int32][]*NPC // NPCs indexed by appearance ID
database Database // Database interface
logger Logger // Logger interface
spellManager SpellManager // Spell system interface
skillManager SkillManager // Skill system interface
appearanceManager AppearanceManager // Appearance system interface
mutex sync.RWMutex // Thread safety
npcs map[int32]*NPC // NPCs indexed by ID
npcsByZone map[int32][]*NPC // NPCs indexed by zone ID
npcsByAppearance map[int32][]*NPC // NPCs indexed by appearance ID
database Database // Database interface
logger Logger // Logger interface
spellManager SpellManager // Spell system interface
skillManager SkillManager // Skill system interface
appearanceManager AppearanceManager // Appearance system interface
mutex sync.RWMutex // Thread safety
// Statistics
totalNPCs int64
npcsInCombat int64
spellCastCount int64
skillUsageCount int64
runbackCount int64
aiStrategyCounts map[int8]int64
totalNPCs int64
npcsInCombat int64
spellCastCount int64
skillUsageCount int64
runbackCount int64
aiStrategyCounts map[int8]int64
// Configuration
maxNPCs int32

View File

@ -15,35 +15,35 @@ import (
// NewNPC creates a new NPC with default values
func NewNPC() *NPC {
npc := &NPC{
Entity: entity.NewEntity(),
appearanceID: 0,
npcID: 0,
aiStrategy: AIStrategyBalanced,
attackType: 0,
castPercentage: DefaultCastPercentage,
maxPetLevel: DefaultMaxPetLevel,
aggroRadius: DefaultAggroRadius,
baseAggroRadius: DefaultAggroRadius,
runback: nil,
runningBack: false,
runbackHeadingDir1: 0,
runbackHeadingDir2: 0,
pauseTimer: NewTimer(),
primarySpellList: 0,
secondarySpellList: 0,
primarySkillList: 0,
secondarySkillList: 0,
equipmentListID: 0,
skills: make(map[string]*Skill),
spells: make([]*NPCSpell, 0),
castOnSpells: make(map[int8][]*NPCSpell),
skillBonuses: make(map[int32]*SkillBonus),
hasSpells: false,
castOnAggroCompleted: false,
shardID: 0,
shardCharID: 0,
shardCreatedTimestamp: 0,
callRunback: false,
Entity: entity.NewEntity(),
appearanceID: 0,
npcID: 0,
aiStrategy: AIStrategyBalanced,
attackType: 0,
castPercentage: DefaultCastPercentage,
maxPetLevel: DefaultMaxPetLevel,
aggroRadius: DefaultAggroRadius,
baseAggroRadius: DefaultAggroRadius,
runback: nil,
runningBack: false,
runbackHeadingDir1: 0,
runbackHeadingDir2: 0,
pauseTimer: NewTimer(),
primarySpellList: 0,
secondarySpellList: 0,
primarySkillList: 0,
secondarySkillList: 0,
equipmentListID: 0,
skills: make(map[string]*Skill),
spells: make([]*NPCSpell, 0),
castOnSpells: make(map[int8][]*NPCSpell),
skillBonuses: make(map[int32]*SkillBonus),
hasSpells: false,
castOnAggroCompleted: false,
shardID: 0,
shardCharID: 0,
shardCreatedTimestamp: 0,
callRunback: false,
}
// Initialize cast-on spell arrays

View File

@ -11,24 +11,24 @@ import (
// NPCSpell represents a spell configuration for NPCs
type NPCSpell struct {
ListID int32 // Spell list identifier
SpellID int32 // Spell ID from master spell list
Tier int8 // Spell tier
CastOnSpawn bool // Cast when NPC spawns
CastOnInitialAggro bool // Cast when first entering combat
RequiredHPRatio int8 // HP ratio requirement for casting (-100 to 100)
mutex sync.RWMutex
ListID int32 // Spell list identifier
SpellID int32 // Spell ID from master spell list
Tier int8 // Spell tier
CastOnSpawn bool // Cast when NPC spawns
CastOnInitialAggro bool // Cast when first entering combat
RequiredHPRatio int8 // HP ratio requirement for casting (-100 to 100)
mutex sync.RWMutex
}
// NewNPCSpell creates a new NPCSpell
func NewNPCSpell() *NPCSpell {
return &NPCSpell{
ListID: 0,
SpellID: 0,
Tier: 1,
CastOnSpawn: false,
CastOnInitialAggro: false,
RequiredHPRatio: 0,
ListID: 0,
SpellID: 0,
Tier: 1,
CastOnSpawn: false,
CastOnInitialAggro: false,
RequiredHPRatio: 0,
}
}
@ -38,12 +38,12 @@ func (ns *NPCSpell) Copy() *NPCSpell {
defer ns.mutex.RUnlock()
return &NPCSpell{
ListID: ns.ListID,
SpellID: ns.SpellID,
Tier: ns.Tier,
CastOnSpawn: ns.CastOnSpawn,
CastOnInitialAggro: ns.CastOnInitialAggro,
RequiredHPRatio: ns.RequiredHPRatio,
ListID: ns.ListID,
SpellID: ns.SpellID,
Tier: ns.Tier,
CastOnSpawn: ns.CastOnSpawn,
CastOnInitialAggro: ns.CastOnInitialAggro,
RequiredHPRatio: ns.RequiredHPRatio,
}
}
@ -123,8 +123,8 @@ func (ns *NPCSpell) SetRequiredHPRatio(ratio int8) {
// SkillBonus represents a skill bonus from spells
type SkillBonus struct {
SpellID int32 // Spell providing the bonus
Skills map[int32]*SkillBonusValue // Map of skill ID to bonus value
SpellID int32 // Spell providing the bonus
Skills map[int32]*SkillBonusValue // Map of skill ID to bonus value
mutex sync.RWMutex
}
@ -222,60 +222,60 @@ func (ml *MovementLocation) Copy() *MovementLocation {
// NPC represents a non-player character extending Entity
type NPC struct {
*entity.Entity // Embedded entity for combat capabilities
*entity.Entity // Embedded entity for combat capabilities
// Core NPC properties
appearanceID int32 // Appearance ID for client display
npcID int32 // NPC database ID
aiStrategy int8 // AI strategy (balanced/offensive/defensive)
attackType int8 // Attack type preference
castPercentage int8 // Percentage chance to cast spells
maxPetLevel int8 // Maximum pet level
appearanceID int32 // Appearance ID for client display
npcID int32 // NPC database ID
aiStrategy int8 // AI strategy (balanced/offensive/defensive)
attackType int8 // Attack type preference
castPercentage int8 // Percentage chance to cast spells
maxPetLevel int8 // Maximum pet level
// Combat and movement
aggroRadius float32 // Aggro detection radius
baseAggroRadius float32 // Base aggro radius (for resets)
runback *MovementLocation // Runback location when leaving combat
runningBack bool // Currently running back to spawn point
runbackHeadingDir1 int16 // Original heading direction 1
runbackHeadingDir2 int16 // Original heading direction 2
pauseTimer *Timer // Movement pause timer
aggroRadius float32 // Aggro detection radius
baseAggroRadius float32 // Base aggro radius (for resets)
runback *MovementLocation // Runback location when leaving combat
runningBack bool // Currently running back to spawn point
runbackHeadingDir1 int16 // Original heading direction 1
runbackHeadingDir2 int16 // Original heading direction 2
pauseTimer *Timer // Movement pause timer
// Spell and skill management
primarySpellList int32 // Primary spell list ID
secondarySpellList int32 // Secondary spell list ID
primarySkillList int32 // Primary skill list ID
secondarySkillList int32 // Secondary skill list ID
equipmentListID int32 // Equipment list ID
skills map[string]*Skill // NPC skills by name
spells []*NPCSpell // Available spells
castOnSpells map[int8][]*NPCSpell // Spells to cast by trigger type
skillBonuses map[int32]*SkillBonus // Skill bonuses from spells
hasSpells bool // Whether NPC has any spells
castOnAggroCompleted bool // Whether cast-on-aggro spells are done
primarySpellList int32 // Primary spell list ID
secondarySpellList int32 // Secondary spell list ID
primarySkillList int32 // Primary skill list ID
secondarySkillList int32 // Secondary skill list ID
equipmentListID int32 // Equipment list ID
skills map[string]*Skill // NPC skills by name
spells []*NPCSpell // Available spells
castOnSpells map[int8][]*NPCSpell // Spells to cast by trigger type
skillBonuses map[int32]*SkillBonus // Skill bonuses from spells
hasSpells bool // Whether NPC has any spells
castOnAggroCompleted bool // Whether cast-on-aggro spells are done
// Brain/AI system (placeholder for now)
brain Brain // AI brain for decision making
brain Brain // AI brain for decision making
// Shard system (for cross-server functionality)
shardID int32 // Shard identifier
shardCharID int32 // Character ID on shard
shardCreatedTimestamp int64 // Timestamp when created on shard
shardID int32 // Shard identifier
shardCharID int32 // Character ID on shard
shardCreatedTimestamp int64 // Timestamp when created on shard
// Thread safety
mutex sync.RWMutex // Main NPC mutex
brainMutex sync.RWMutex // Brain-specific mutex
mutex sync.RWMutex // Main NPC mutex
brainMutex sync.RWMutex // Brain-specific mutex
// Atomic flags for thread-safe state management
callRunback bool // Flag to trigger runback
callRunback bool // Flag to trigger runback
}
// Timer represents a simple timer for NPC operations
type Timer struct {
duration time.Duration
startTime time.Time
enabled bool
mutex sync.RWMutex
duration time.Duration
startTime time.Time
enabled bool
mutex sync.RWMutex
}
// NewTimer creates a new timer
@ -325,10 +325,10 @@ func (t *Timer) Disable() {
// Skill represents an NPC skill (simplified from C++ version)
type Skill struct {
SkillID int32 // Skill identifier
Name string // Skill name
CurrentVal int16 // Current skill value
MaxVal int16 // Maximum skill value
SkillID int32 // Skill identifier
Name string // Skill name
CurrentVal int16 // Current skill value
MaxVal int16 // Maximum skill value
mutex sync.RWMutex
}
@ -428,13 +428,13 @@ func (b *DefaultBrain) SetActive(active bool) {
// NPCStatistics contains NPC system statistics
type NPCStatistics struct {
TotalNPCs int `json:"total_npcs"`
NPCsInCombat int `json:"npcs_in_combat"`
NPCsWithSpells int `json:"npcs_with_spells"`
NPCsWithSkills int `json:"npcs_with_skills"`
AIStrategyCounts map[string]int `json:"ai_strategy_counts"`
SpellCastCount int64 `json:"spell_cast_count"`
SkillUsageCount int64 `json:"skill_usage_count"`
RunbackCount int64 `json:"runback_count"`
AverageAggroRadius float32 `json:"average_aggro_radius"`
TotalNPCs int `json:"total_npcs"`
NPCsInCombat int `json:"npcs_in_combat"`
NPCsWithSpells int `json:"npcs_with_spells"`
NPCsWithSkills int `json:"npcs_with_skills"`
AIStrategyCounts map[string]int `json:"ai_strategy_counts"`
SpellCastCount int64 `json:"spell_cast_count"`
SkillUsageCount int64 `json:"skill_usage_count"`
RunbackCount int64 `json:"runback_count"`
AverageAggroRadius float32 `json:"average_aggro_radius"`
}

View File

@ -7,8 +7,8 @@ const (
// Object appearance defaults (from C++ constructor)
ObjectActivityStatus = 64 // Default activity status
ObjectPosState = 1 // Default position state
ObjectDifficulty = 0 // Default difficulty
ObjectPosState = 1 // Default position state
ObjectDifficulty = 0 // Default difficulty
// Object interaction constants
ObjectShowCommandIcon = 1 // Show command icon when interactable

View File

@ -4,7 +4,6 @@ import (
"fmt"
"eq2emu/internal/spawn"
"eq2emu/internal/common"
)
// ObjectSpawn represents an object that extends spawn functionality
@ -13,8 +12,8 @@ type ObjectSpawn struct {
*spawn.Spawn // Embed the spawn functionality
// Object-specific properties
clickable bool // Whether the object can be clicked/interacted with
deviceID int8 // Device ID for interactive objects
clickable bool // Whether the object can be clicked/interacted with
deviceID int8 // Device ID for interactive objects
}
// NewObjectSpawn creates a new object spawn with default values
@ -145,7 +144,7 @@ func (os *ObjectSpawn) GetObjectInfo() map[string]interface{} {
// ObjectSpawnManager manages object spawns specifically
type ObjectSpawnManager struct {
spawnManager *spawn.SpawnManager // Reference to global spawn manager
spawnManager *spawn.SpawnManager // Reference to global spawn manager
objects map[int32]*ObjectSpawn // Object spawns by spawn ID
}

View File

@ -167,14 +167,14 @@ func (osae *ObjectSpawnAsEntity) SetClientVersion(version int32) {
// ObjectItem represents an item provided by an object (merchants, containers, etc.)
type ObjectItem struct {
id int32
name string
quantity int32
iconID int32
noTrade bool
heirloom bool
attuned bool
creationTime time.Time
id int32
name string
quantity int32
iconID int32
noTrade bool
heirloom bool
attuned bool
creationTime time.Time
groupCharacterIDs []int32
}

View File

@ -19,33 +19,33 @@ type Object struct {
deviceID int8 // Device ID for interactive objects
// Inherited spawn properties (placeholder until spawn integration)
databaseID int32
size int16
sizeOffset int8
merchantID int32
merchantType int8
merchantMinLevel int8
merchantMaxLevel int8
isCollector bool
factionID int32
totalHP int32
totalPower int32
currentHP int32
currentPower int32
transporterID int32
soundsDisabled bool
omittedByDBFlag bool
lootTier int8
lootDropType int8
spawnScript string
spawnScriptSetDB bool
primaryCommandListID int32
secondaryCommandListID int32
databaseID int32
size int16
sizeOffset int8
merchantID int32
merchantType int8
merchantMinLevel int8
merchantMaxLevel int8
isCollector bool
factionID int32
totalHP int32
totalPower int32
currentHP int32
currentPower int32
transporterID int32
soundsDisabled bool
omittedByDBFlag bool
lootTier int8
lootDropType int8
spawnScript string
spawnScriptSetDB bool
primaryCommandListID int32
secondaryCommandListID int32
// Appearance data placeholder - TODO: Use actual appearance struct
appearanceActivityStatus int8
appearancePosState int8
appearanceDifficulty int8
appearanceActivityStatus int8
appearancePosState int8
appearanceDifficulty int8
appearanceShowCommandIcon int8
// Command lists - TODO: Use actual command structures
@ -60,15 +60,15 @@ type Object struct {
// Converted from C++ Object::Object constructor
func NewObject() *Object {
return &Object{
clickable: false,
zoneName: "",
deviceID: DeviceIDNone,
appearanceActivityStatus: ObjectActivityStatus,
appearancePosState: ObjectPosState,
appearanceDifficulty: ObjectDifficulty,
clickable: false,
zoneName: "",
deviceID: DeviceIDNone,
appearanceActivityStatus: ObjectActivityStatus,
appearancePosState: ObjectPosState,
appearanceDifficulty: ObjectDifficulty,
appearanceShowCommandIcon: 0,
primaryCommands: make([]string, 0),
secondaryCommands: make([]string, 0),
primaryCommands: make([]string, 0),
secondaryCommands: make([]string, 0),
}
}

View File

@ -2,97 +2,97 @@ package player
// Character flag constants
const (
CF_COMBAT_EXPERIENCE_ENABLED = 0
CF_ENABLE_CHANGE_LASTNAME = 1
CF_FOOD_AUTO_CONSUME = 2
CF_DRINK_AUTO_CONSUME = 3
CF_AUTO_ATTACK = 4
CF_RANGED_AUTO_ATTACK = 5
CF_QUEST_EXPERIENCE_ENABLED = 6
CF_CHASE_CAMERA_MAYBE = 7
CF_100 = 8
CF_200 = 9
CF_IS_SITTING = 10 // Can't cast or attack
CF_800 = 11
CF_ANONYMOUS = 12
CF_ROLEPLAYING = 13
CF_AFK = 14
CF_LFG = 15
CF_LFW = 16
CF_HIDE_HOOD = 17
CF_HIDE_HELM = 18
CF_SHOW_ILLUSION = 19
CF_ALLOW_DUEL_INVITES = 20
CF_ALLOW_TRADE_INVITES = 21
CF_ALLOW_GROUP_INVITES = 22
CF_ALLOW_RAID_INVITES = 23
CF_ALLOW_GUILD_INVITES = 24
CF_2000000 = 25
CF_4000000 = 26
CF_DEFENSE_SKILLS_AT_MAX_QUESTIONABLE = 27
CF_SHOW_GUILD_HERALDRY = 28
CF_SHOW_CLOAK = 29
CF_IN_PVP = 30
CF_IS_HATED = 31
CF2_1 = 32
CF2_2 = 33
CF2_4 = 34
CF2_ALLOW_LON_INVITES = 35
CF2_SHOW_RANGED = 36
CF2_ALLOW_VOICE_INVITES = 37
CF_COMBAT_EXPERIENCE_ENABLED = 0
CF_ENABLE_CHANGE_LASTNAME = 1
CF_FOOD_AUTO_CONSUME = 2
CF_DRINK_AUTO_CONSUME = 3
CF_AUTO_ATTACK = 4
CF_RANGED_AUTO_ATTACK = 5
CF_QUEST_EXPERIENCE_ENABLED = 6
CF_CHASE_CAMERA_MAYBE = 7
CF_100 = 8
CF_200 = 9
CF_IS_SITTING = 10 // Can't cast or attack
CF_800 = 11
CF_ANONYMOUS = 12
CF_ROLEPLAYING = 13
CF_AFK = 14
CF_LFG = 15
CF_LFW = 16
CF_HIDE_HOOD = 17
CF_HIDE_HELM = 18
CF_SHOW_ILLUSION = 19
CF_ALLOW_DUEL_INVITES = 20
CF_ALLOW_TRADE_INVITES = 21
CF_ALLOW_GROUP_INVITES = 22
CF_ALLOW_RAID_INVITES = 23
CF_ALLOW_GUILD_INVITES = 24
CF_2000000 = 25
CF_4000000 = 26
CF_DEFENSE_SKILLS_AT_MAX_QUESTIONABLE = 27
CF_SHOW_GUILD_HERALDRY = 28
CF_SHOW_CLOAK = 29
CF_IN_PVP = 30
CF_IS_HATED = 31
CF2_1 = 32
CF2_2 = 33
CF2_4 = 34
CF2_ALLOW_LON_INVITES = 35
CF2_SHOW_RANGED = 36
CF2_ALLOW_VOICE_INVITES = 37
CF2_CHARACTER_BONUS_EXPERIENCE_ENABLED = 38
CF2_80 = 39
CF2_100 = 40 // Hide achievements
CF2_200 = 41
CF2_400 = 42
CF2_800 = 43 // Enable facebook updates
CF2_1000 = 44 // Enable twitter updates
CF2_2000 = 45 // Enable eq2 player updates
CF2_4000 = 46 // EQ2 players, link to alt chars
CF2_8000 = 47
CF2_10000 = 48
CF2_20000 = 49
CF2_40000 = 50
CF2_80000 = 51
CF2_100000 = 52
CF2_200000 = 53
CF2_400000 = 54
CF2_800000 = 55
CF2_1000000 = 56
CF2_2000000 = 57
CF2_4000000 = 58
CF2_8000000 = 59
CF2_10000000 = 60
CF2_20000000 = 61
CF2_40000000 = 62
CF2_80000000 = 63
CF_MAXIMUM_FLAG = 63
CF_HIDE_STATUS = 49 // For testing only
CF_GM_HIDDEN = 50 // For testing only
CF2_80 = 39
CF2_100 = 40 // Hide achievements
CF2_200 = 41
CF2_400 = 42
CF2_800 = 43 // Enable facebook updates
CF2_1000 = 44 // Enable twitter updates
CF2_2000 = 45 // Enable eq2 player updates
CF2_4000 = 46 // EQ2 players, link to alt chars
CF2_8000 = 47
CF2_10000 = 48
CF2_20000 = 49
CF2_40000 = 50
CF2_80000 = 51
CF2_100000 = 52
CF2_200000 = 53
CF2_400000 = 54
CF2_800000 = 55
CF2_1000000 = 56
CF2_2000000 = 57
CF2_4000000 = 58
CF2_8000000 = 59
CF2_10000000 = 60
CF2_20000000 = 61
CF2_40000000 = 62
CF2_80000000 = 63
CF_MAXIMUM_FLAG = 63
CF_HIDE_STATUS = 49 // For testing only
CF_GM_HIDDEN = 50 // For testing only
)
// Update activity constants
const (
UPDATE_ACTIVITY_FALLING = 0
UPDATE_ACTIVITY_RUNNING = 128
UPDATE_ACTIVITY_RIDING_BOAT = 256
UPDATE_ACTIVITY_JUMPING = 1024
UPDATE_ACTIVITY_IN_WATER_ABOVE = 6144
UPDATE_ACTIVITY_IN_WATER_BELOW = 6272
UPDATE_ACTIVITY_SITTING = 6336
UPDATE_ACTIVITY_DROWNING = 14464
UPDATE_ACTIVITY_DROWNING2 = 14336
UPDATE_ACTIVITY_FALLING = 0
UPDATE_ACTIVITY_RUNNING = 128
UPDATE_ACTIVITY_RIDING_BOAT = 256
UPDATE_ACTIVITY_JUMPING = 1024
UPDATE_ACTIVITY_IN_WATER_ABOVE = 6144
UPDATE_ACTIVITY_IN_WATER_BELOW = 6272
UPDATE_ACTIVITY_SITTING = 6336
UPDATE_ACTIVITY_DROWNING = 14464
UPDATE_ACTIVITY_DROWNING2 = 14336
// Age of Malice (AOM) variants
UPDATE_ACTIVITY_FALLING_AOM = 16384
UPDATE_ACTIVITY_RIDING_BOAT_AOM = 256
UPDATE_ACTIVITY_RUNNING_AOM = 16512
UPDATE_ACTIVITY_JUMPING_AOM = 17408
UPDATE_ACTIVITY_FALLING_AOM = 16384
UPDATE_ACTIVITY_RIDING_BOAT_AOM = 256
UPDATE_ACTIVITY_RUNNING_AOM = 16512
UPDATE_ACTIVITY_JUMPING_AOM = 17408
UPDATE_ACTIVITY_MOVE_WATER_BELOW_AOM = 22528
UPDATE_ACTIVITY_MOVE_WATER_ABOVE_AOM = 22656
UPDATE_ACTIVITY_SITTING_AOM = 22720
UPDATE_ACTIVITY_DROWNING_AOM = 30720
UPDATE_ACTIVITY_DROWNING2_AOM = 30848
UPDATE_ACTIVITY_SITTING_AOM = 22720
UPDATE_ACTIVITY_DROWNING_AOM = 30720
UPDATE_ACTIVITY_DROWNING2_AOM = 30848
)
// Effect slot constants
@ -128,11 +128,11 @@ const (
// Quickbar type constants
const (
QUICKBAR_NORMAL = 1
QUICKBAR_INV_SLOT = 2
QUICKBAR_MACRO = 3
QUICKBAR_TEXT_CMD = 4
QUICKBAR_ITEM = 6
QUICKBAR_NORMAL = 1
QUICKBAR_INV_SLOT = 2
QUICKBAR_MACRO = 3
QUICKBAR_TEXT_CMD = 4
QUICKBAR_ITEM = 6
)
// Combat state constants
@ -163,14 +163,14 @@ const (
// Add item type constants
const (
AddItemTypeNOT_SET = 0
AddItemTypeQUEST = 1
AddItemTypeBUY_FROM_MERCHANT = 2
AddItemTypeLOOT = 3
AddItemTypeTRADE = 4
AddItemTypeMAIL = 5
AddItemTypeHOUSE = 6
AddItemTypeCRAFT = 7
AddItemTypeCOLLECTION_REWARD = 8
AddItemTypeNOT_SET = 0
AddItemTypeQUEST = 1
AddItemTypeBUY_FROM_MERCHANT = 2
AddItemTypeLOOT = 3
AddItemTypeTRADE = 4
AddItemTypeMAIL = 5
AddItemTypeHOUSE = 6
AddItemTypeCRAFT = 7
AddItemTypeCOLLECTION_REWARD = 8
AddItemTypeTRADESKILL_ACHIEVEMENT = 9
)

View File

@ -1,7 +1,8 @@
package player
import (
"math"
"eq2emu/internal/entity"
"time"
)
// GetXPVitality returns the player's adventure XP vitality
@ -141,7 +142,7 @@ func (p *Player) AddTSXP(xpAmount int32) bool {
overflow := totalXP - neededXP
// Level up
p.SetTSLevel(p.GetTSLevel()+1)
p.SetTSLevel(p.GetTSLevel() + 1)
p.SetNeededTSXPByLevel()
// Set XP to overflow amount

View File

@ -12,14 +12,14 @@ import (
type Manager struct {
// Players indexed by various keys
playersLock sync.RWMutex
players map[int32]*Player // playerID -> Player
playersByName map[string]*Player // name -> Player (case insensitive)
playersByCharID map[int32]*Player // characterID -> Player
playersByZone map[int32][]*Player // zoneID -> []*Player
players map[int32]*Player // playerID -> Player
playersByName map[string]*Player // name -> Player (case insensitive)
playersByCharID map[int32]*Player // characterID -> Player
playersByZone map[int32][]*Player // zoneID -> []*Player
// Player statistics
stats PlayerStats
statsLock sync.RWMutex
stats PlayerStats
statsLock sync.RWMutex
// Event handlers
eventHandlers []PlayerEventHandler

View File

@ -1,19 +1,11 @@
package player
import (
"fmt"
"sync"
"sync/atomic"
"time"
"eq2emu/internal/common"
"eq2emu/internal/entity"
"eq2emu/internal/factions"
"eq2emu/internal/languages"
"eq2emu/internal/quests"
"eq2emu/internal/skills"
"eq2emu/internal/spells"
"eq2emu/internal/titles"
)
// Global XP table
@ -32,37 +24,37 @@ func NewPlayer() *Player {
houseVaultSlots: 0,
// Initialize maps
playerQuests: make(map[int32]*quests.Quest),
completedQuests: make(map[int32]*quests.Quest),
pendingQuests: make(map[int32]*quests.Quest),
currentQuestFlagged: make(map[*entity.Spawn]bool),
playerSpawnQuestsRequired: make(map[int32][]int32),
playerSpawnHistoryRequired: make(map[int32][]int32),
spawnVisPacketList: make(map[int32]string),
spawnInfoPacketList: make(map[int32]string),
spawnPosPacketList: make(map[int32]string),
spawnPacketSent: make(map[int32]int8),
spawnStateList: make(map[int32]*SpawnQueueState),
playerSpawnIDMap: make(map[int32]*entity.Spawn),
playerSpawnReverseIDMap: make(map[*entity.Spawn]int32),
playerAggroRangeSpawns: make(map[int32]bool),
pendingLootItems: make(map[int32]map[int32]bool),
friendList: make(map[string]int8),
ignoreList: make(map[string]int8),
characterHistory: make(map[int8]map[int8][]*HistoryData),
charLuaHistory: make(map[int32]*LUAHistory),
playersPoiList: make(map[int32][]int32),
playerQuests: make(map[int32]*quests.Quest),
completedQuests: make(map[int32]*quests.Quest),
pendingQuests: make(map[int32]*quests.Quest),
currentQuestFlagged: make(map[*entity.Spawn]bool),
playerSpawnQuestsRequired: make(map[int32][]int32),
playerSpawnHistoryRequired: make(map[int32][]int32),
spawnVisPacketList: make(map[int32]string),
spawnInfoPacketList: make(map[int32]string),
spawnPosPacketList: make(map[int32]string),
spawnPacketSent: make(map[int32]int8),
spawnStateList: make(map[int32]*SpawnQueueState),
playerSpawnIDMap: make(map[int32]*entity.Spawn),
playerSpawnReverseIDMap: make(map[*entity.Spawn]int32),
playerAggroRangeSpawns: make(map[int32]bool),
pendingLootItems: make(map[int32]map[int32]bool),
friendList: make(map[string]int8),
ignoreList: make(map[string]int8),
characterHistory: make(map[int8]map[int8][]*HistoryData),
charLuaHistory: make(map[int32]*LUAHistory),
playersPoiList: make(map[int32][]int32),
pendingSelectableItemRewards: make(map[int32][]Item),
statistics: make(map[int32]*Statistic),
mailList: make(map[int32]*Mail),
targetInvisHistory: make(map[int32]bool),
spawnedBots: make(map[int32]int32),
macroIcons: make(map[int32]int16),
sortedTraitList: make(map[int8]map[int8][]*TraitData),
classTraining: make(map[int8][]*TraitData),
raceTraits: make(map[int8][]*TraitData),
innateRaceTraits: make(map[int8][]*TraitData),
focusEffects: make(map[int8][]*TraitData),
statistics: make(map[int32]*Statistic),
mailList: make(map[int32]*Mail),
targetInvisHistory: make(map[int32]bool),
spawnedBots: make(map[int32]int32),
macroIcons: make(map[int32]int16),
sortedTraitList: make(map[int8]map[int8][]*TraitData),
classTraining: make(map[int8][]*TraitData),
raceTraits: make(map[int8][]*TraitData),
innateRaceTraits: make(map[int8][]*TraitData),
focusEffects: make(map[int8][]*TraitData),
}
// Initialize entity base

View File

@ -3,6 +3,7 @@ package player
import (
"eq2emu/internal/entity"
"eq2emu/internal/quests"
"eq2emu/internal/spells"
)
// GetQuest returns a quest by ID

View File

@ -2,7 +2,6 @@ package player
import (
"sort"
"sync"
"eq2emu/internal/spells"
)

View File

@ -30,12 +30,12 @@ const (
// HistoryData represents character history data matching the character_history table
type HistoryData struct {
Value int32
Value2 int32
Location [200]byte
EventID int32
EventDate int32
NeedsSave bool
Value int32
Value2 int32
Location [200]byte
EventID int32
EventDate int32
NeedsSave bool
}
// LUAHistory represents history set through the LUA system
@ -110,15 +110,15 @@ type PlayerLoginAppearance struct {
// InstanceData represents instance information for a player
type InstanceData struct {
DBID int32
InstanceID int32
ZoneID int32
ZoneInstanceType int8
ZoneName string
LastSuccessTimestamp int32
LastFailureTimestamp int32
SuccessLockoutTime int32
FailureLockoutTime int32
DBID int32
InstanceID int32
ZoneID int32
ZoneInstanceType int8
ZoneName string
LastSuccessTimestamp int32
LastFailureTimestamp int32
SuccessLockoutTime int32
FailureLockoutTime int32
}
// CharacterInstances manages all instances for a character
@ -129,31 +129,31 @@ type CharacterInstances struct {
// PlayerInfo contains detailed player information for serialization
type PlayerInfo struct {
player *Player
infoStruct *entity.InfoStruct
houseZoneID int32
bindZoneID int32
bindX float32
bindY float32
bindZ float32
bindHeading float32
boatXOffset float32
boatYOffset float32
boatZOffset float32
boatSpawn int32
changes []byte
origPacket []byte
petChanges []byte
petOrigPacket []byte
player *Player
infoStruct *entity.InfoStruct
houseZoneID int32
bindZoneID int32
bindX float32
bindY float32
bindZ float32
bindHeading float32
boatXOffset float32
boatYOffset float32
boatZOffset float32
boatSpawn int32
changes []byte
origPacket []byte
petChanges []byte
petOrigPacket []byte
}
// PlayerControlFlags manages player control flags
type PlayerControlFlags struct {
flagsChanged bool
flagChanges map[int8]map[int8]int8
currentFlags map[int8]map[int8]bool
controlMutex sync.Mutex
changesMutex sync.Mutex
flagsChanged bool
flagChanges map[int8]map[int8]int8
currentFlags map[int8]map[int8]bool
controlMutex sync.Mutex
changesMutex sync.Mutex
}
// PlayerGroup represents a player's group information
@ -168,9 +168,9 @@ type GroupMemberInfo struct {
// Statistic represents a player statistic
type Statistic struct {
StatID int32
Value int64
Date int32
StatID int32
Value int64
Date int32
}
// Mail represents in-game mail
@ -318,38 +318,38 @@ type Player struct {
group *PlayerGroup
// Movement and position
movementPacket []byte
oldMovementPacket []byte
movementPacket []byte
oldMovementPacket []byte
lastMovementActivity int16
posPacketSpeed float32
testX float32
testY float32
testZ float32
testTime int32
posPacketSpeed float32
testX float32
testY float32
testZ float32
testTime int32
// Combat
rangeAttack bool
combatTarget *entity.Entity
resurrecting bool
rangeAttack bool
combatTarget *entity.Entity
resurrecting bool
// Packet management
packetNum int32
spawnIndex int16
spellCount int16
packetNum int32
spawnIndex int16
spellCount int16
spellOrigPacket []byte
spellXorPacket []byte
raidOrigPacket []byte
raidXorPacket []byte
// Spawn management
spawnVisPacketList map[int32]string
spawnInfoPacketList map[int32]string
spawnPosPacketList map[int32]string
spawnPacketSent map[int32]int8
spawnStateList map[int32]*SpawnQueueState
playerSpawnIDMap map[int32]*entity.Spawn
spawnVisPacketList map[int32]string
spawnInfoPacketList map[int32]string
spawnPosPacketList map[int32]string
spawnPacketSent map[int32]int8
spawnStateList map[int32]*SpawnQueueState
playerSpawnIDMap map[int32]*entity.Spawn
playerSpawnReverseIDMap map[*entity.Spawn]int32
playerAggroRangeSpawns map[int32]bool
playerAggroRangeSpawns map[int32]bool
// Temporary spawn packets for XOR
spawnTmpVisXorPacket []byte
@ -360,13 +360,13 @@ type Player struct {
infoXorSize int32
// Packet structures
spawnPosStruct *PacketStruct
spawnInfoStruct *PacketStruct
spawnVisStruct *PacketStruct
spawnHeaderStruct *PacketStruct
spawnFooterStruct *PacketStruct
spawnPosStruct *PacketStruct
spawnInfoStruct *PacketStruct
spawnVisStruct *PacketStruct
spawnHeaderStruct *PacketStruct
spawnFooterStruct *PacketStruct
widgetFooterStruct *PacketStruct
signFooterStruct *PacketStruct
signFooterStruct *PacketStruct
// Character flags
charsheetChanged atomic.Bool
@ -375,27 +375,27 @@ type Player struct {
quickbarUpdated bool
// Quest system
playerQuests map[int32]*quests.Quest
completedQuests map[int32]*quests.Quest
pendingQuests map[int32]*quests.Quest
currentQuestFlagged map[*entity.Spawn]bool
playerSpawnQuestsRequired map[int32][]int32
playerSpawnHistoryRequired map[int32][]int32
playerQuests map[int32]*quests.Quest
completedQuests map[int32]*quests.Quest
pendingQuests map[int32]*quests.Quest
currentQuestFlagged map[*entity.Spawn]bool
playerSpawnQuestsRequired map[int32][]int32
playerSpawnHistoryRequired map[int32][]int32
// Skills and spells
spells []*SpellBookEntry
passiveSpells []int32
skillList PlayerSkillList
spells []*SpellBookEntry
passiveSpells []int32
skillList PlayerSkillList
allSpellsLocked bool
// Items and equipment
itemList PlayerItemList
quickbarItems []*QuickBarItem
itemList PlayerItemList
quickbarItems []*QuickBarItem
pendingLootItems map[int32]map[int32]bool
// Social lists
friendList map[string]int8
ignoreList map[string]int8
friendList map[string]int8
ignoreList map[string]int8
// Character history
characterHistory map[int8]map[int8][]*HistoryData
@ -405,12 +405,12 @@ type Player struct {
playersPoiList map[int32][]int32
// Collections and achievements
collectionList PlayerCollectionList
pendingCollectionReward *Collection
pendingItemRewards []Item
collectionList PlayerCollectionList
pendingCollectionReward *Collection
pendingItemRewards []Item
pendingSelectableItemRewards map[int32][]Item
achievementList PlayerAchievementList
achievementUpdateList PlayerAchievementUpdateList
achievementList PlayerAchievementList
achievementUpdateList PlayerAchievementUpdateList
// Titles and languages
playerTitlesList PlayerTitlesList
@ -435,17 +435,17 @@ type Player struct {
characterInstances CharacterInstances
// Character state
awayMessage string
biography string
isTracking bool
pendingDeletion bool
returningFromLD bool
custNPC bool
custNPCTarget *entity.Entity
awayMessage string
biography string
isTracking bool
pendingDeletion bool
returningFromLD bool
custNPC bool
custNPCTarget *entity.Entity
stopSaveSpellEffects bool
gmVision bool
resetMentorship bool
activeReward bool
gmVision bool
resetMentorship bool
activeReward bool
// Guild
guild *Guild
@ -482,31 +482,31 @@ type Player struct {
houseVaultSlots int8
// Traits
sortedTraitList map[int8]map[int8][]*TraitData
classTraining map[int8][]*TraitData
raceTraits map[int8][]*TraitData
innateRaceTraits map[int8][]*TraitData
focusEffects map[int8][]*TraitData
needTraitUpdate atomic.Bool
sortedTraitList map[int8]map[int8][]*TraitData
classTraining map[int8][]*TraitData
raceTraits map[int8][]*TraitData
innateRaceTraits map[int8][]*TraitData
focusEffects map[int8][]*TraitData
needTraitUpdate atomic.Bool
// Mutexes
playerQuestsMutex sync.RWMutex
spellsBookMutex sync.RWMutex
recipeBookMutex sync.RWMutex
playerSpawnQuestsRequiredMutex sync.RWMutex
playerQuestsMutex sync.RWMutex
spellsBookMutex sync.RWMutex
recipeBookMutex sync.RWMutex
playerSpawnQuestsRequiredMutex sync.RWMutex
playerSpawnHistoryRequiredMutex sync.RWMutex
luaHistoryMutex sync.RWMutex
controlFlagsMutex sync.RWMutex
infoMutex sync.RWMutex
posMutex sync.RWMutex
visMutex sync.RWMutex
indexMutex sync.RWMutex
spawnMutex sync.RWMutex
spawnAggroRangeMutex sync.RWMutex
traitMutex sync.RWMutex
spellPacketUpdateMutex sync.RWMutex
raidUpdateMutex sync.RWMutex
mailMutex sync.RWMutex
luaHistoryMutex sync.RWMutex
controlFlagsMutex sync.RWMutex
infoMutex sync.RWMutex
posMutex sync.RWMutex
visMutex sync.RWMutex
indexMutex sync.RWMutex
spawnMutex sync.RWMutex
spawnAggroRangeMutex sync.RWMutex
traitMutex sync.RWMutex
spellPacketUpdateMutex sync.RWMutex
raidUpdateMutex sync.RWMutex
mailMutex sync.RWMutex
}
// SkillBonus represents a skill bonus from a spell

View File

@ -1,5 +1,10 @@
package quests
import (
"fmt"
"strings"
)
// Action management methods for Quest
// AddCompleteAction adds a completion action for a step
@ -339,7 +344,7 @@ func (q *Quest) validateLuaSyntax(luaCode string) error {
// Check for obviously invalid syntax patterns
invalidPatterns := []string{
"--[[", // Unfinished multi-line comments
"--[[", // Unfinished multi-line comments
"function(", // Function without closing
}

View File

@ -2,28 +2,28 @@ package quests
// Quest step type constants
const (
StepTypeKill int8 = 1
StepTypeChat int8 = 2
StepTypeObtainItem int8 = 3
StepTypeLocation int8 = 4
StepTypeSpell int8 = 5
StepTypeNormal int8 = 6
StepTypeCraft int8 = 7
StepTypeHarvest int8 = 8
StepTypeKillRaceReq int8 = 9 // kill using race type requirement instead of npc db id
StepTypeKill int8 = 1
StepTypeChat int8 = 2
StepTypeObtainItem int8 = 3
StepTypeLocation int8 = 4
StepTypeSpell int8 = 5
StepTypeNormal int8 = 6
StepTypeCraft int8 = 7
StepTypeHarvest int8 = 8
StepTypeKillRaceReq int8 = 9 // kill using race type requirement instead of npc db id
)
// Quest display status constants
const (
DisplayStatusHidden int32 = 0
DisplayStatusNoCheck int32 = 1
DisplayStatusYellow int32 = 2
DisplayStatusCompleted int32 = 4
DisplayStatusRepeatable int32 = 8
DisplayStatusCanShare int32 = 16
DisplayStatusHidden int32 = 0
DisplayStatusNoCheck int32 = 1
DisplayStatusYellow int32 = 2
DisplayStatusCompleted int32 = 4
DisplayStatusRepeatable int32 = 8
DisplayStatusCanShare int32 = 16
DisplayStatusCompleteFlag int32 = 32
DisplayStatusShow int32 = 64
DisplayStatusCheck int32 = 128
DisplayStatusShow int32 = 64
DisplayStatusCheck int32 = 128
)
// Quest shareable flags

View File

@ -1,5 +1,7 @@
package quests
import "fmt"
// Player interface defines the required player functionality for quest system
type Player interface {
// Basic player information
@ -155,11 +157,11 @@ type QuestRewardProcessor interface {
// QuestRewards contains calculated quest rewards
type QuestRewards struct {
Coins int64 `json:"coins"`
Experience int32 `json:"experience"`
TSExperience int32 `json:"ts_experience"`
StatusPoints int32 `json:"status_points"`
Items []Item `json:"items"`
Coins int64 `json:"coins"`
Experience int32 `json:"experience"`
TSExperience int32 `json:"ts_experience"`
StatusPoints int32 `json:"status_points"`
Items []Item `json:"items"`
FactionRewards map[int32]int32 `json:"faction_rewards"`
}

View File

@ -246,11 +246,11 @@ func (mql *MasterQuestList) GetQuestStatistics() *QuestStatistics {
// QuestStatistics contains statistical information about quests
type QuestStatistics struct {
TotalQuests int `json:"total_quests"`
QuestsByType map[string]int `json:"quests_by_type"`
QuestsByLevel map[int8]int `json:"quests_by_level"`
RepeatableCount int `json:"repeatable_count"`
HiddenCount int `json:"hidden_count"`
TotalQuests int `json:"total_quests"`
QuestsByType map[string]int `json:"quests_by_type"`
QuestsByLevel map[int8]int `json:"quests_by_level"`
RepeatableCount int `json:"repeatable_count"`
HiddenCount int `json:"hidden_count"`
}
// QuestManager provides high-level quest management functionality
@ -443,8 +443,8 @@ func (qm *QuestManager) GetPlayerQuestStatistics(playerID int32) *PlayerQuestSta
// PlayerQuestStatistics contains statistical information about a player's quests
type PlayerQuestStatistics struct {
TotalQuests int `json:"total_quests"`
CompletedQuests int `json:"completed_quests"`
QuestsByType map[string]int `json:"quests_by_type"`
QuestsByLevel map[int8]int `json:"quests_by_level"`
TotalQuests int `json:"total_quests"`
CompletedQuests int `json:"completed_quests"`
QuestsByType map[string]int `json:"quests_by_type"`
QuestsByLevel map[int8]int `json:"quests_by_level"`
}

View File

@ -1,5 +1,7 @@
package quests
import "fmt"
// Prerequisite management methods for Quest
// SetPrereqLevel sets the minimum level requirement

View File

@ -1,5 +1,7 @@
package quests
import "fmt"
// Reward management methods for Quest
// AddRewardCoins adds coin rewards

View File

@ -3,16 +3,15 @@ package quests
import (
"sync"
"time"
"eq2emu/internal/common"
)
// Location represents a 3D location in a zone for quest steps
type Location struct {
ID int32 `json:"id"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
ZoneID int32 `json:"zone_id"`
ID int32 `json:"id"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
ZoneID int32 `json:"zone_id"`
}
// NewLocation creates a new location
@ -45,25 +44,25 @@ func NewQuestFactionPrereq(factionID, min, max int32) *QuestFactionPrereq {
// QuestStep represents a single step in a quest
type QuestStep struct {
// Basic step data
ID int32 `json:"step_id"`
Type int8 `json:"type"`
Description string `json:"description"`
TaskGroup string `json:"task_group"`
Quantity int32 `json:"quantity"`
StepProgress int32 `json:"step_progress"`
Icon int16 `json:"icon"`
MaxVariation float32 `json:"max_variation"`
Percentage float32 `json:"percentage"`
UsableItemID int32 `json:"usable_item_id"`
ID int32 `json:"step_id"`
Type int8 `json:"type"`
Description string `json:"description"`
TaskGroup string `json:"task_group"`
Quantity int32 `json:"quantity"`
StepProgress int32 `json:"step_progress"`
Icon int16 `json:"icon"`
MaxVariation float32 `json:"max_variation"`
Percentage float32 `json:"percentage"`
UsableItemID int32 `json:"usable_item_id"`
// Tracking data
UpdateName string `json:"update_name"`
UpdateTargetName string `json:"update_target_name"`
Updated bool `json:"updated"`
UpdateName string `json:"update_name"`
UpdateTargetName string `json:"update_target_name"`
Updated bool `json:"updated"`
// Step data (one of these will be populated based on type)
IDs map[int32]bool `json:"ids,omitempty"` // For kill, chat, obtain item, etc.
Locations []*Location `json:"locations,omitempty"` // For location steps
IDs map[int32]bool `json:"ids,omitempty"` // For kill, chat, obtain item, etc.
Locations []*Location `json:"locations,omitempty"` // For location steps
// Thread safety
mutex sync.RWMutex
@ -323,100 +322,100 @@ func (qs *QuestStep) SetIcon(icon int16) {
// Quest represents a complete quest with all its steps and requirements
type Quest struct {
// Basic quest information
ID int32 `json:"quest_id"`
Name string `json:"name"`
Type string `json:"type"`
Zone string `json:"zone"`
Level int8 `json:"level"`
EncounterLevel int8 `json:"encounter_level"`
Description string `json:"description"`
CompletedDesc string `json:"completed_description"`
ID int32 `json:"quest_id"`
Name string `json:"name"`
Type string `json:"type"`
Zone string `json:"zone"`
Level int8 `json:"level"`
EncounterLevel int8 `json:"encounter_level"`
Description string `json:"description"`
CompletedDesc string `json:"completed_description"`
// Quest giver and return NPC
QuestGiver int32 `json:"quest_giver"`
ReturnID int32 `json:"return_id"`
QuestGiver int32 `json:"quest_giver"`
ReturnID int32 `json:"return_id"`
// Prerequisites
PrereqLevel int8 `json:"prereq_level"`
PrereqTSLevel int8 `json:"prereq_ts_level"`
PrereqMaxLevel int8 `json:"prereq_max_level"`
PrereqMaxTSLevel int8 `json:"prereq_max_ts_level"`
PrereqFactions []*QuestFactionPrereq `json:"prereq_factions"`
PrereqRaces []int8 `json:"prereq_races"`
PrereqModelTypes []int16 `json:"prereq_model_types"`
PrereqClasses []int8 `json:"prereq_classes"`
PrereqTSClasses []int8 `json:"prereq_ts_classes"`
PrereqQuests []int32 `json:"prereq_quests"`
PrereqLevel int8 `json:"prereq_level"`
PrereqTSLevel int8 `json:"prereq_ts_level"`
PrereqMaxLevel int8 `json:"prereq_max_level"`
PrereqMaxTSLevel int8 `json:"prereq_max_ts_level"`
PrereqFactions []*QuestFactionPrereq `json:"prereq_factions"`
PrereqRaces []int8 `json:"prereq_races"`
PrereqModelTypes []int16 `json:"prereq_model_types"`
PrereqClasses []int8 `json:"prereq_classes"`
PrereqTSClasses []int8 `json:"prereq_ts_classes"`
PrereqQuests []int32 `json:"prereq_quests"`
// Rewards
RewardCoins int64 `json:"reward_coins"`
RewardCoinsMax int64 `json:"reward_coins_max"`
RewardFactions map[int32]int32 `json:"reward_factions"`
RewardStatus int32 `json:"reward_status"`
RewardComment string `json:"reward_comment"`
RewardExp int32 `json:"reward_exp"`
RewardTSExp int32 `json:"reward_ts_exp"`
GeneratedCoin int64 `json:"generated_coin"`
RewardCoins int64 `json:"reward_coins"`
RewardCoinsMax int64 `json:"reward_coins_max"`
RewardFactions map[int32]int32 `json:"reward_factions"`
RewardStatus int32 `json:"reward_status"`
RewardComment string `json:"reward_comment"`
RewardExp int32 `json:"reward_exp"`
RewardTSExp int32 `json:"reward_ts_exp"`
GeneratedCoin int64 `json:"generated_coin"`
// Temporary rewards
TmpRewardStatus int32 `json:"tmp_reward_status"`
TmpRewardCoins int64 `json:"tmp_reward_coins"`
TmpRewardStatus int32 `json:"tmp_reward_status"`
TmpRewardCoins int64 `json:"tmp_reward_coins"`
// Steps and task groups
QuestSteps []*QuestStep `json:"quest_steps"`
QuestStepMap map[int32]*QuestStep `json:"-"` // For quick lookup
QuestStepReverseMap map[*QuestStep]int32 `json:"-"` // Reverse lookup
StepUpdates []*QuestStep `json:"-"` // Steps that were updated
StepFailures []*QuestStep `json:"-"` // Steps that failed
TaskGroupOrder map[int16]string `json:"task_group_order"`
TaskGroup map[string][]*QuestStep `json:"-"` // Grouped steps
TaskGroupNum int16 `json:"task_group_num"`
QuestSteps []*QuestStep `json:"quest_steps"`
QuestStepMap map[int32]*QuestStep `json:"-"` // For quick lookup
QuestStepReverseMap map[*QuestStep]int32 `json:"-"` // Reverse lookup
StepUpdates []*QuestStep `json:"-"` // Steps that were updated
StepFailures []*QuestStep `json:"-"` // Steps that failed
TaskGroupOrder map[int16]string `json:"task_group_order"`
TaskGroup map[string][]*QuestStep `json:"-"` // Grouped steps
TaskGroupNum int16 `json:"task_group_num"`
// Actions
CompleteActions map[int32]string `json:"complete_actions"`
ProgressActions map[int32]string `json:"progress_actions"`
FailedActions map[int32]string `json:"failed_actions"`
CompleteAction string `json:"complete_action"`
CompleteActions map[int32]string `json:"complete_actions"`
ProgressActions map[int32]string `json:"progress_actions"`
FailedActions map[int32]string `json:"failed_actions"`
CompleteAction string `json:"complete_action"`
// State tracking
Deleted bool `json:"deleted"`
TurnedIn bool `json:"turned_in"`
UpdateNeeded bool `json:"update_needed"`
HasSentLastUpdate bool `json:"has_sent_last_update"`
NeedsSave bool `json:"needs_save"`
Visible int8 `json:"visible"`
Deleted bool `json:"deleted"`
TurnedIn bool `json:"turned_in"`
UpdateNeeded bool `json:"update_needed"`
HasSentLastUpdate bool `json:"has_sent_last_update"`
NeedsSave bool `json:"needs_save"`
Visible int8 `json:"visible"`
// Date tracking
Day int8 `json:"day"`
Month int8 `json:"month"`
Year int8 `json:"year"`
Day int8 `json:"day"`
Month int8 `json:"month"`
Year int8 `json:"year"`
// Quest flags and settings
FeatherColor int8 `json:"feather_color"`
Repeatable bool `json:"repeatable"`
Tracked bool `json:"tracked"`
CompletedFlag bool `json:"completed_flag"`
YellowName bool `json:"yellow_name"`
QuestFlags int32 `json:"quest_flags"`
Hidden bool `json:"hidden"`
Status int32 `json:"status"`
FeatherColor int8 `json:"feather_color"`
Repeatable bool `json:"repeatable"`
Tracked bool `json:"tracked"`
CompletedFlag bool `json:"completed_flag"`
YellowName bool `json:"yellow_name"`
QuestFlags int32 `json:"quest_flags"`
Hidden bool `json:"hidden"`
Status int32 `json:"status"`
// Timer and completion tracking
Timestamp int32 `json:"timestamp"`
TimerStep int32 `json:"timer_step"`
CompleteCount int16 `json:"complete_count"`
Timestamp int32 `json:"timestamp"`
TimerStep int32 `json:"timer_step"`
CompleteCount int16 `json:"complete_count"`
// Temporary state
QuestStateTemporary bool `json:"quest_state_temporary"`
QuestTempDescription string `json:"quest_temp_description"`
QuestShareableFlag int32 `json:"quest_shareable_flag"`
CanDeleteQuest bool `json:"can_delete_quest"`
StatusToEarnMin int32 `json:"status_to_earn_min"`
StatusToEarnMax int32 `json:"status_to_earn_max"`
HideReward bool `json:"hide_reward"`
QuestStateTemporary bool `json:"quest_state_temporary"`
QuestTempDescription string `json:"quest_temp_description"`
QuestShareableFlag int32 `json:"quest_shareable_flag"`
CanDeleteQuest bool `json:"can_delete_quest"`
StatusToEarnMin int32 `json:"status_to_earn_min"`
StatusToEarnMax int32 `json:"status_to_earn_max"`
HideReward bool `json:"hide_reward"`
// Thread safety
stepsMutex sync.RWMutex
stepsMutex sync.RWMutex
completeActionsMutex sync.RWMutex
progressActionsMutex sync.RWMutex
failedActionsMutex sync.RWMutex
@ -427,46 +426,46 @@ func NewQuest(id int32) *Quest {
now := time.Now()
quest := &Quest{
ID: id,
PrereqLevel: DefaultPrereqLevel,
PrereqTSLevel: 0,
PrereqMaxLevel: 0,
PrereqMaxTSLevel: 0,
RewardCoins: 0,
RewardCoinsMax: 0,
CompletedFlag: false,
HasSentLastUpdate: false,
EncounterLevel: 0,
RewardExp: 0,
RewardTSExp: 0,
FeatherColor: 0,
Repeatable: false,
YellowName: false,
Hidden: false,
GeneratedCoin: 0,
QuestFlags: 0,
Timestamp: 0,
CompleteCount: 0,
QuestStateTemporary: false,
TmpRewardStatus: 0,
TmpRewardCoins: 0,
CompletedDesc: "",
ID: id,
PrereqLevel: DefaultPrereqLevel,
PrereqTSLevel: 0,
PrereqMaxLevel: 0,
PrereqMaxTSLevel: 0,
RewardCoins: 0,
RewardCoinsMax: 0,
CompletedFlag: false,
HasSentLastUpdate: false,
EncounterLevel: 0,
RewardExp: 0,
RewardTSExp: 0,
FeatherColor: 0,
Repeatable: false,
YellowName: false,
Hidden: false,
GeneratedCoin: 0,
QuestFlags: 0,
Timestamp: 0,
CompleteCount: 0,
QuestStateTemporary: false,
TmpRewardStatus: 0,
TmpRewardCoins: 0,
CompletedDesc: "",
QuestTempDescription: "",
QuestShareableFlag: 0,
CanDeleteQuest: false,
Status: 0,
StatusToEarnMin: 0,
StatusToEarnMax: 0,
HideReward: false,
Deleted: false,
TurnedIn: false,
UpdateNeeded: true,
NeedsSave: false,
TaskGroupNum: DefaultTaskGroupNum,
Visible: DefaultVisible,
Day: int8(now.Day()),
Month: int8(now.Month()),
Year: int8(now.Year() - 2000), // EQ2 uses 2-digit years
QuestShareableFlag: 0,
CanDeleteQuest: false,
Status: 0,
StatusToEarnMin: 0,
StatusToEarnMax: 0,
HideReward: false,
Deleted: false,
TurnedIn: false,
UpdateNeeded: true,
NeedsSave: false,
TaskGroupNum: DefaultTaskGroupNum,
Visible: DefaultVisible,
Day: int8(now.Day()),
Month: int8(now.Month()),
Year: int8(now.Year() - 2000), // EQ2 uses 2-digit years
// Initialize maps and slices
QuestStepMap: make(map[int32]*QuestStep),

View File

@ -88,39 +88,39 @@ func (r *Races) initializeRaces() {
// Initialize good races (from C++ race_map_good)
// "Neutral" races appear in both lists for /randomize functionality
r.goodRaces = []string{
RaceNameDwarf, // 0
RaceNameFaeLight, // 1
RaceNameFroglok, // 2
RaceNameHalfling, // 3
RaceNameHighElf, // 4
RaceNameWoodElf, // 5
RaceNameBarbarian, // 6 (neutral)
RaceNameErudite, // 7 (neutral)
RaceNameGnome, // 8 (neutral)
RaceNameHalfElf, // 9 (neutral)
RaceNameHuman, // 10 (neutral)
RaceNameKerra, // 11 (neutral)
RaceNameVampire, // 12 (neutral)
RaceNameAerakyn, // 13 (neutral)
RaceNameDwarf, // 0
RaceNameFaeLight, // 1
RaceNameFroglok, // 2
RaceNameHalfling, // 3
RaceNameHighElf, // 4
RaceNameWoodElf, // 5
RaceNameBarbarian, // 6 (neutral)
RaceNameErudite, // 7 (neutral)
RaceNameGnome, // 8 (neutral)
RaceNameHalfElf, // 9 (neutral)
RaceNameHuman, // 10 (neutral)
RaceNameKerra, // 11 (neutral)
RaceNameVampire, // 12 (neutral)
RaceNameAerakyn, // 13 (neutral)
}
// Initialize evil races (from C++ race_map_evil)
r.evilRaces = []string{
RaceNameFaeDark, // 0
RaceNameDarkElf, // 1
RaceNameIksar, // 2
RaceNameOgre, // 3
RaceNameRatonga, // 4
RaceNameSarnak, // 5
RaceNameTroll, // 6
RaceNameBarbarian, // 7 (neutral)
RaceNameErudite, // 8 (neutral)
RaceNameGnome, // 9 (neutral)
RaceNameHalfElf, // 10 (neutral)
RaceNameHuman, // 11 (neutral)
RaceNameKerra, // 12 (neutral)
RaceNameVampire, // 13 (neutral)
RaceNameAerakyn, // 14 (neutral)
RaceNameFaeDark, // 0
RaceNameDarkElf, // 1
RaceNameIksar, // 2
RaceNameOgre, // 3
RaceNameRatonga, // 4
RaceNameSarnak, // 5
RaceNameTroll, // 6
RaceNameBarbarian, // 7 (neutral)
RaceNameErudite, // 8 (neutral)
RaceNameGnome, // 9 (neutral)
RaceNameHalfElf, // 10 (neutral)
RaceNameHuman, // 11 (neutral)
RaceNameKerra, // 12 (neutral)
RaceNameVampire, // 13 (neutral)
RaceNameAerakyn, // 14 (neutral)
}
}

View File

@ -1,7 +1,6 @@
package races
import (
"fmt"
"math/rand"
"strings"
)

View File

@ -37,24 +37,24 @@ const (
// Tradeskill class bitmasks
// These are used to determine which classes can use a recipe
const (
TradeskillAny = 3 // 1+2: Any class can use
TradeskillAdornment = 1 // Adornment recipes
TradeskillArtisan = 2 // Base artisan recipes
TradeskillAny = 3 // 1+2: Any class can use
TradeskillAdornment = 1 // Adornment recipes
TradeskillArtisan = 2 // Base artisan recipes
// Base tradeskill classes (bits 0-12)
TradeskillProvisioner = 1 << 0
TradeskillWoodworker = 1 << 1
TradeskillCarpenter = 1 << 2
TradeskillOutfitter = 1 << 3
TradeskillArmorer = 1 << 4
TradeskillWeaponsmith = 1 << 5
TradeskillTailor = 1 << 6
TradeskillScholar = 1 << 7
TradeskillJeweler = 1 << 8
TradeskillSage = 1 << 9
TradeskillAlchemist = 1 << 10
TradeskillCraftsman = 1 << 11
TradeskillTinkerer = 1 << 12
TradeskillProvisioner = 1 << 0
TradeskillWoodworker = 1 << 1
TradeskillCarpenter = 1 << 2
TradeskillOutfitter = 1 << 3
TradeskillArmorer = 1 << 4
TradeskillWeaponsmith = 1 << 5
TradeskillTailor = 1 << 6
TradeskillScholar = 1 << 7
TradeskillJeweler = 1 << 8
TradeskillSage = 1 << 9
TradeskillAlchemist = 1 << 10
TradeskillCraftsman = 1 << 11
TradeskillTinkerer = 1 << 12
)
// Crafting device types
@ -90,30 +90,30 @@ const (
// Error variables
var (
ErrRecipeNotFound = errors.New("recipe not found")
ErrRecipeBookNotFound = errors.New("recipe book not found")
ErrInvalidRecipeID = errors.New("invalid recipe ID")
ErrInvalidRecipeData = errors.New("invalid recipe data")
ErrDuplicateRecipe = errors.New("duplicate recipe ID")
ErrDuplicateRecipeBook = errors.New("duplicate recipe book ID")
ErrMissingComponents = errors.New("missing required components")
ErrInsufficientSkill = errors.New("insufficient skill level")
ErrWrongTradeskillClass = errors.New("wrong tradeskill class")
ErrWrongDevice = errors.New("wrong crafting device")
ErrCannotLearnRecipe = errors.New("cannot learn recipe")
ErrCannotUseRecipeBook = errors.New("cannot use recipe book")
ErrCraftingInProgress = errors.New("crafting session in progress")
ErrInvalidCraftingStage = errors.New("invalid crafting stage")
ErrRecipeNotFound = errors.New("recipe not found")
ErrRecipeBookNotFound = errors.New("recipe book not found")
ErrInvalidRecipeID = errors.New("invalid recipe ID")
ErrInvalidRecipeData = errors.New("invalid recipe data")
ErrDuplicateRecipe = errors.New("duplicate recipe ID")
ErrDuplicateRecipeBook = errors.New("duplicate recipe book ID")
ErrMissingComponents = errors.New("missing required components")
ErrInsufficientSkill = errors.New("insufficient skill level")
ErrWrongTradeskillClass = errors.New("wrong tradeskill class")
ErrWrongDevice = errors.New("wrong crafting device")
ErrCannotLearnRecipe = errors.New("cannot learn recipe")
ErrCannotUseRecipeBook = errors.New("cannot use recipe book")
ErrCraftingInProgress = errors.New("crafting session in progress")
ErrInvalidCraftingStage = errors.New("invalid crafting stage")
ErrCraftingSessionNotFound = errors.New("crafting session not found")
)
// Database table and column names
const (
TableRecipes = "recipe"
TableRecipeComponents = "recipe_comp_list_item"
TableRecipeSecondaryComp = "recipe_secondary_comp"
TableCharacterRecipes = "character_recipes"
TableRecipes = "recipe"
TableRecipeComponents = "recipe_comp_list_item"
TableRecipeSecondaryComp = "recipe_secondary_comp"
TableCharacterRecipes = "character_recipes"
TableCharacterRecipeBooks = "character_recipe_books"
TableItems = "items"
TableItemDetailsRecipe = "item_details_recipe_items"
TableItems = "items"
TableItemDetailsRecipe = "item_details_recipe_items"
)

View File

@ -9,13 +9,13 @@ import (
// RecipeManager provides high-level recipe system management with database integration
type RecipeManager struct {
db *database.DB
masterRecipeList *MasterRecipeList
masterRecipeBookList *MasterRecipeBookList
loadedRecipes map[int32]*Recipe
loadedRecipeBooks map[int32]*Recipe
mu sync.RWMutex
statisticsEnabled bool
db *database.DB
masterRecipeList *MasterRecipeList
masterRecipeBookList *MasterRecipeBookList
loadedRecipes map[int32]*Recipe
loadedRecipeBooks map[int32]*Recipe
mu sync.RWMutex
statisticsEnabled bool
// Statistics
stats RecipeManagerStats
@ -34,12 +34,12 @@ type RecipeManagerStats struct {
// NewRecipeManager creates a new recipe manager with database integration
func NewRecipeManager(db *database.DB) *RecipeManager {
return &RecipeManager{
db: db,
masterRecipeList: NewMasterRecipeList(),
masterRecipeBookList: NewMasterRecipeBookList(),
loadedRecipes: make(map[int32]*Recipe),
loadedRecipeBooks: make(map[int32]*Recipe),
statisticsEnabled: true,
db: db,
masterRecipeList: NewMasterRecipeList(),
masterRecipeBookList: NewMasterRecipeBookList(),
loadedRecipes: make(map[int32]*Recipe),
loadedRecipeBooks: make(map[int32]*Recipe),
statisticsEnabled: true,
}
}

View File

@ -8,14 +8,14 @@ import (
// MasterRecipeList manages all recipes in the system
// Converted from C++ MasterRecipeList class
type MasterRecipeList struct {
recipes map[int32]*Recipe // Recipe ID -> Recipe
recipesCRC map[int32]*Recipe // SOE CRC ID -> Recipe
nameIndex map[string]*Recipe // Lowercase name -> Recipe
bookIndex map[string][]*Recipe // Lowercase book name -> Recipes
skillIndex map[int32][]*Recipe // Skill ID -> Recipes
tierIndex map[int8][]*Recipe // Tier -> Recipes
mutex sync.RWMutex
stats *Statistics
recipes map[int32]*Recipe // Recipe ID -> Recipe
recipesCRC map[int32]*Recipe // SOE CRC ID -> Recipe
nameIndex map[string]*Recipe // Lowercase name -> Recipe
bookIndex map[string][]*Recipe // Lowercase book name -> Recipes
skillIndex map[int32][]*Recipe // Skill ID -> Recipes
tierIndex map[int8][]*Recipe // Tier -> Recipes
mutex sync.RWMutex
stats *Statistics
}
// NewMasterRecipeList creates a new master recipe list

View File

@ -3,7 +3,6 @@ package recipes
import (
"fmt"
"strings"
"sync"
)
// NewRecipe creates a new recipe with default values

View File

@ -25,7 +25,7 @@ type RecipeProducts struct {
type Recipe struct {
// Core recipe data
ID int32
SoeID int32 // SOE recipe ID (CRC)
SoeID int32 // SOE recipe ID (CRC)
BookID int32
Name string
Description string
@ -34,14 +34,14 @@ type Recipe struct {
Device string
// Recipe requirements and properties
Level int8
Tier int8
Icon int16
Skill int32
Technique int32
Knowledge int32
Classes int32 // Bitmask of tradeskill classes
DeviceSubType int8
Level int8
Tier int8
Icon int16
Skill int32
Technique int32
Knowledge int32
Classes int32 // Bitmask of tradeskill classes
DeviceSubType int8
// Unknown fields from C++ (preserved for compatibility)
Unknown1 int8
@ -87,15 +87,15 @@ type Recipe struct {
// Statistics tracks recipe system usage patterns
type Statistics struct {
TotalRecipes int32
TotalRecipeBooks int32
RecipesByTier map[int8]int32
RecipesBySkill map[int32]int32
RecipeLookups int64
RecipeBookLookups int64
PlayerRecipeLoads int64
ComponentQueries int64
mutex sync.RWMutex
TotalRecipes int32
TotalRecipeBooks int32
RecipesByTier map[int8]int32
RecipesBySkill map[int32]int32
RecipeLookups int64
RecipeBookLookups int64
PlayerRecipeLoads int64
ComponentQueries int64
mutex sync.RWMutex
}
// NewStatistics creates a new statistics tracker
@ -140,14 +140,14 @@ func (s *Statistics) GetSnapshot() Statistics {
defer s.mutex.RUnlock()
snapshot := Statistics{
TotalRecipes: s.TotalRecipes,
TotalRecipeBooks: s.TotalRecipeBooks,
RecipesByTier: make(map[int8]int32),
RecipesBySkill: make(map[int32]int32),
RecipeLookups: s.RecipeLookups,
RecipeBookLookups: s.RecipeBookLookups,
PlayerRecipeLoads: s.PlayerRecipeLoads,
ComponentQueries: s.ComponentQueries,
TotalRecipes: s.TotalRecipes,
TotalRecipeBooks: s.TotalRecipeBooks,
RecipesByTier: make(map[int8]int32),
RecipesBySkill: make(map[int32]int32),
RecipeLookups: s.RecipeLookups,
RecipeBookLookups: s.RecipeBookLookups,
PlayerRecipeLoads: s.PlayerRecipeLoads,
ComponentQueries: s.ComponentQueries,
}
for tier, count := range s.RecipesByTier {

View File

@ -27,9 +27,9 @@ type RuleType int32
// CLIENT RULES
const (
ClientShowWelcomeScreen RuleType = 0 // Show welcome screen to new players
ClientGroupSpellsTimer RuleType = 1 // Group spells update timer
ClientQuestQueueTimer RuleType = 2 // Quest queue processing timer
ClientShowWelcomeScreen RuleType = 0 // Show welcome screen to new players
ClientGroupSpellsTimer RuleType = 1 // Group spells update timer
ClientQuestQueueTimer RuleType = 2 // Quest queue processing timer
)
// FACTION RULES
@ -45,262 +45,262 @@ const (
// PLAYER RULES
const (
PlayerMaxLevel RuleType = 0 // Maximum player level
PlayerMaxLevelOverrideStatus RuleType = 1 // Status required to override max level
PlayerMaxPlayers RuleType = 2 // Maximum players on server
PlayerMaxPlayersOverrideStatus RuleType = 3 // Status required to override max players
PlayerVitalityAmount RuleType = 4 // Vitality bonus amount
PlayerVitalityFrequency RuleType = 5 // Vitality bonus frequency
PlayerMaxAA RuleType = 6 // Maximum total AA points
PlayerMaxClassAA RuleType = 7 // Maximum class AA points
PlayerMaxSubclassAA RuleType = 8 // Maximum subclass AA points
PlayerMaxShadowsAA RuleType = 9 // Maximum shadows AA points
PlayerMaxHeroicAA RuleType = 10 // Maximum heroic AA points
PlayerMaxTradeskillAA RuleType = 11 // Maximum tradeskill AA points
PlayerMaxPrestigeAA RuleType = 12 // Maximum prestige AA points
PlayerMaxTradeskillPrestigeAA RuleType = 13 // Maximum tradeskill prestige AA points
PlayerMaxDragonAA RuleType = 14 // Maximum dragon AA points
PlayerMinLastNameLevel RuleType = 15 // Minimum level for last name
PlayerMaxLastNameLength RuleType = 16 // Maximum last name length
PlayerMinLastNameLength RuleType = 17 // Minimum last name length
PlayerDisableHouseAlignmentRequirement RuleType = 18 // Disable house alignment requirement
PlayerMentorItemDecayRate RuleType = 19 // Item decay rate when mentoring
PlayerTemporaryItemLogoutTime RuleType = 20 // Time for temporary items to decay
PlayerHeirloomItemShareExpiration RuleType = 21 // Heirloom item sharing expiration
PlayerSwimmingSkillMinSpeed RuleType = 22 // Minimum swimming speed
PlayerSwimmingSkillMaxSpeed RuleType = 23 // Maximum swimming speed
PlayerSwimmingSkillMinBreathLength RuleType = 24 // Minimum breath length
PlayerSwimmingSkillMaxBreathLength RuleType = 25 // Maximum breath length
PlayerAutoSkillUpBaseSkills RuleType = 26 // Auto-skill base skills on level
PlayerMaxWeightStrengthMultiplier RuleType = 27 // Strength multiplier for max weight
PlayerBaseWeight RuleType = 28 // Base weight for all classes
PlayerWeightPercentImpact RuleType = 29 // Speed impact per weight percent
PlayerWeightPercentCap RuleType = 30 // Maximum weight impact cap
PlayerCoinWeightPerStone RuleType = 31 // Coin weight per stone
PlayerWeightInflictsSpeed RuleType = 32 // Whether weight affects speed
PlayerLevelMasterySkillMultiplier RuleType = 33 // Level mastery skill multiplier
PlayerTraitTieringSelection RuleType = 34 // Trait tiering selection rules
PlayerClassicTraitLevelTable RuleType = 35 // Use classic trait level table
PlayerTraitFocusSelectLevel RuleType = 36 // Trait focus selection level
PlayerTraitTrainingSelectLevel RuleType = 37 // Trait training selection level
PlayerTraitRaceSelectLevel RuleType = 38 // Trait race selection level
PlayerTraitCharacterSelectLevel RuleType = 39 // Trait character selection level
PlayerStartHPBase RuleType = 40 // Starting HP base
PlayerStartPowerBase RuleType = 41 // Starting power base
PlayerStartHPLevelMod RuleType = 42 // HP level modifier
PlayerStartPowerLevelMod RuleType = 43 // Power level modifier
PlayerAllowEquipCombat RuleType = 44 // Allow equipment changes in combat
PlayerMaxTargetCommandDistance RuleType = 45 // Max distance for target command
PlayerMinSkillMultiplierValue RuleType = 46 // Min skill multiplier value
PlayerHarvestSkillUpMultiplier RuleType = 47 // Harvest skill up multiplier
PlayerMiniDingPercentage RuleType = 48 // Mini ding percentage
PlayerMaxLevel RuleType = 0 // Maximum player level
PlayerMaxLevelOverrideStatus RuleType = 1 // Status required to override max level
PlayerMaxPlayers RuleType = 2 // Maximum players on server
PlayerMaxPlayersOverrideStatus RuleType = 3 // Status required to override max players
PlayerVitalityAmount RuleType = 4 // Vitality bonus amount
PlayerVitalityFrequency RuleType = 5 // Vitality bonus frequency
PlayerMaxAA RuleType = 6 // Maximum total AA points
PlayerMaxClassAA RuleType = 7 // Maximum class AA points
PlayerMaxSubclassAA RuleType = 8 // Maximum subclass AA points
PlayerMaxShadowsAA RuleType = 9 // Maximum shadows AA points
PlayerMaxHeroicAA RuleType = 10 // Maximum heroic AA points
PlayerMaxTradeskillAA RuleType = 11 // Maximum tradeskill AA points
PlayerMaxPrestigeAA RuleType = 12 // Maximum prestige AA points
PlayerMaxTradeskillPrestigeAA RuleType = 13 // Maximum tradeskill prestige AA points
PlayerMaxDragonAA RuleType = 14 // Maximum dragon AA points
PlayerMinLastNameLevel RuleType = 15 // Minimum level for last name
PlayerMaxLastNameLength RuleType = 16 // Maximum last name length
PlayerMinLastNameLength RuleType = 17 // Minimum last name length
PlayerDisableHouseAlignmentRequirement RuleType = 18 // Disable house alignment requirement
PlayerMentorItemDecayRate RuleType = 19 // Item decay rate when mentoring
PlayerTemporaryItemLogoutTime RuleType = 20 // Time for temporary items to decay
PlayerHeirloomItemShareExpiration RuleType = 21 // Heirloom item sharing expiration
PlayerSwimmingSkillMinSpeed RuleType = 22 // Minimum swimming speed
PlayerSwimmingSkillMaxSpeed RuleType = 23 // Maximum swimming speed
PlayerSwimmingSkillMinBreathLength RuleType = 24 // Minimum breath length
PlayerSwimmingSkillMaxBreathLength RuleType = 25 // Maximum breath length
PlayerAutoSkillUpBaseSkills RuleType = 26 // Auto-skill base skills on level
PlayerMaxWeightStrengthMultiplier RuleType = 27 // Strength multiplier for max weight
PlayerBaseWeight RuleType = 28 // Base weight for all classes
PlayerWeightPercentImpact RuleType = 29 // Speed impact per weight percent
PlayerWeightPercentCap RuleType = 30 // Maximum weight impact cap
PlayerCoinWeightPerStone RuleType = 31 // Coin weight per stone
PlayerWeightInflictsSpeed RuleType = 32 // Whether weight affects speed
PlayerLevelMasterySkillMultiplier RuleType = 33 // Level mastery skill multiplier
PlayerTraitTieringSelection RuleType = 34 // Trait tiering selection rules
PlayerClassicTraitLevelTable RuleType = 35 // Use classic trait level table
PlayerTraitFocusSelectLevel RuleType = 36 // Trait focus selection level
PlayerTraitTrainingSelectLevel RuleType = 37 // Trait training selection level
PlayerTraitRaceSelectLevel RuleType = 38 // Trait race selection level
PlayerTraitCharacterSelectLevel RuleType = 39 // Trait character selection level
PlayerStartHPBase RuleType = 40 // Starting HP base
PlayerStartPowerBase RuleType = 41 // Starting power base
PlayerStartHPLevelMod RuleType = 42 // HP level modifier
PlayerStartPowerLevelMod RuleType = 43 // Power level modifier
PlayerAllowEquipCombat RuleType = 44 // Allow equipment changes in combat
PlayerMaxTargetCommandDistance RuleType = 45 // Max distance for target command
PlayerMinSkillMultiplierValue RuleType = 46 // Min skill multiplier value
PlayerHarvestSkillUpMultiplier RuleType = 47 // Harvest skill up multiplier
PlayerMiniDingPercentage RuleType = 48 // Mini ding percentage
)
// PVP RULES
const (
PVPAllowPVP RuleType = 0 // Allow PVP combat
PVPLevelRange RuleType = 1 // PVP level range
PVPInvisPlayerDiscoveryRange RuleType = 2 // Invisible player discovery range
PVPMitigationModByLevel RuleType = 3 // PVP mitigation modifier by level
PVPType RuleType = 4 // PVP type (FFA, alignment, etc.)
PVPAllowPVP RuleType = 0 // Allow PVP combat
PVPLevelRange RuleType = 1 // PVP level range
PVPInvisPlayerDiscoveryRange RuleType = 2 // Invisible player discovery range
PVPMitigationModByLevel RuleType = 3 // PVP mitigation modifier by level
PVPType RuleType = 4 // PVP type (FFA, alignment, etc.)
)
// COMBAT RULES
const (
CombatMaxRange RuleType = 0 // Maximum combat range
CombatDeathExperienceDebt RuleType = 1 // Experience debt on death
CombatGroupExperienceDebt RuleType = 2 // Share debt with group
CombatPVPDeathExperienceDebt RuleType = 3 // PVP death experience debt
CombatExperienceToDebt RuleType = 4 // Percentage of experience to debt
CombatExperienceDebtRecoveryPercent RuleType = 5 // Debt recovery percentage
CombatExperienceDebtRecoveryPeriod RuleType = 6 // Debt recovery period
CombatEnableSpiritShards RuleType = 7 // Enable spirit shards
CombatSpiritShardSpawnScript RuleType = 8 // Spirit shard spawn script
CombatShardDebtRecoveryPercent RuleType = 9 // Shard debt recovery percentage
CombatShardRecoveryByRadius RuleType = 10 // Shard recovery by radius
CombatShardLifetime RuleType = 11 // Shard lifetime
CombatEffectiveMitigationCapLevel RuleType = 12 // Effective mitigation cap level
CombatCalculatedMitigationCapLevel RuleType = 13 // Calculated mitigation cap level
CombatMaxRange RuleType = 0 // Maximum combat range
CombatDeathExperienceDebt RuleType = 1 // Experience debt on death
CombatGroupExperienceDebt RuleType = 2 // Share debt with group
CombatPVPDeathExperienceDebt RuleType = 3 // PVP death experience debt
CombatExperienceToDebt RuleType = 4 // Percentage of experience to debt
CombatExperienceDebtRecoveryPercent RuleType = 5 // Debt recovery percentage
CombatExperienceDebtRecoveryPeriod RuleType = 6 // Debt recovery period
CombatEnableSpiritShards RuleType = 7 // Enable spirit shards
CombatSpiritShardSpawnScript RuleType = 8 // Spirit shard spawn script
CombatShardDebtRecoveryPercent RuleType = 9 // Shard debt recovery percentage
CombatShardRecoveryByRadius RuleType = 10 // Shard recovery by radius
CombatShardLifetime RuleType = 11 // Shard lifetime
CombatEffectiveMitigationCapLevel RuleType = 12 // Effective mitigation cap level
CombatCalculatedMitigationCapLevel RuleType = 13 // Calculated mitigation cap level
CombatMitigationLevelEffectivenessMax RuleType = 14 // Max mitigation effectiveness
CombatMitigationLevelEffectivenessMin RuleType = 15 // Min mitigation effectiveness
CombatMaxMitigationAllowed RuleType = 16 // Max mitigation allowed PVE
CombatMaxMitigationAllowedPVP RuleType = 17 // Max mitigation allowed PVP
CombatStrengthNPC RuleType = 18 // NPC strength multiplier
CombatStrengthOther RuleType = 19 // Other strength multiplier
CombatMaxSkillBonusByLevel RuleType = 20 // Max skill bonus by level
CombatLockedEncounterNoAttack RuleType = 21 // Locked encounter no attack
CombatMaxChaseDistance RuleType = 22 // Maximum chase distance
CombatMaxMitigationAllowed RuleType = 16 // Max mitigation allowed PVE
CombatMaxMitigationAllowedPVP RuleType = 17 // Max mitigation allowed PVP
CombatStrengthNPC RuleType = 18 // NPC strength multiplier
CombatStrengthOther RuleType = 19 // Other strength multiplier
CombatMaxSkillBonusByLevel RuleType = 20 // Max skill bonus by level
CombatLockedEncounterNoAttack RuleType = 21 // Locked encounter no attack
CombatMaxChaseDistance RuleType = 22 // Maximum chase distance
)
// SPAWN RULES
const (
SpawnSpeedMultiplier RuleType = 0 // Speed multiplier
SpawnClassicRegen RuleType = 1 // Use classic regeneration
SpawnHailMovementPause RuleType = 2 // Hail movement pause time
SpawnHailDistance RuleType = 3 // Hail distance
SpawnUseHardCodeWaterModelType RuleType = 4 // Use hardcoded water model type
SpawnSpeedMultiplier RuleType = 0 // Speed multiplier
SpawnClassicRegen RuleType = 1 // Use classic regeneration
SpawnHailMovementPause RuleType = 2 // Hail movement pause time
SpawnHailDistance RuleType = 3 // Hail distance
SpawnUseHardCodeWaterModelType RuleType = 4 // Use hardcoded water model type
SpawnUseHardCodeFlyingModelType RuleType = 5 // Use hardcoded flying model type
)
// UI RULES
const (
UIMaxWhoResults RuleType = 0 // Maximum /who results
UIMaxWhoOverrideStatus RuleType = 1 // Status to override max /who results
UIMaxWhoResults RuleType = 0 // Maximum /who results
UIMaxWhoOverrideStatus RuleType = 1 // Status to override max /who results
)
// WORLD RULES
const (
WorldDefaultStartingZoneID RuleType = 0 // Default starting zone ID
WorldEnablePOIDiscovery RuleType = 1 // Enable POI discovery
WorldGamblingTokenItemID RuleType = 2 // Gambling token item ID
WorldGuildAutoJoin RuleType = 3 // Auto join guild
WorldGuildAutoJoinID RuleType = 4 // Auto join guild ID
WorldGuildAutoJoinDefaultRankID RuleType = 5 // Auto join default rank ID
WorldServerLocked RuleType = 6 // Server locked
WorldServerLockedOverrideStatus RuleType = 7 // Server locked override status
WorldSyncZonesWithLogin RuleType = 8 // Sync zones with login
WorldSyncEquipWithLogin RuleType = 9 // Sync equipment with login
WorldUseBannedIPsTable RuleType = 10 // Use banned IPs table
WorldLinkDeadTimer RuleType = 11 // Link dead timer
WorldDefaultStartingZoneID RuleType = 0 // Default starting zone ID
WorldEnablePOIDiscovery RuleType = 1 // Enable POI discovery
WorldGamblingTokenItemID RuleType = 2 // Gambling token item ID
WorldGuildAutoJoin RuleType = 3 // Auto join guild
WorldGuildAutoJoinID RuleType = 4 // Auto join guild ID
WorldGuildAutoJoinDefaultRankID RuleType = 5 // Auto join default rank ID
WorldServerLocked RuleType = 6 // Server locked
WorldServerLockedOverrideStatus RuleType = 7 // Server locked override status
WorldSyncZonesWithLogin RuleType = 8 // Sync zones with login
WorldSyncEquipWithLogin RuleType = 9 // Sync equipment with login
WorldUseBannedIPsTable RuleType = 10 // Use banned IPs table
WorldLinkDeadTimer RuleType = 11 // Link dead timer
WorldRemoveDisconnectedClientsTimer RuleType = 12 // Remove disconnected clients timer
WorldPlayerCampTimer RuleType = 13 // Player camp timer
WorldGMCampTimer RuleType = 14 // GM camp timer
WorldAutoAdminPlayers RuleType = 15 // Auto admin players
WorldAutoAdminGMs RuleType = 16 // Auto admin GMs
WorldAutoAdminStatusValue RuleType = 17 // Auto admin status value
WorldDuskTime RuleType = 18 // Dusk time
WorldDawnTime RuleType = 19 // Dawn time
WorldThreadedLoad RuleType = 20 // Threaded loading
WorldTradeskillSuccessChance RuleType = 21 // Tradeskill success chance
WorldTradeskillCritSuccessChance RuleType = 22 // Tradeskill critical success chance
WorldTradeskillFailChance RuleType = 23 // Tradeskill fail chance
WorldTradeskillCritFailChance RuleType = 24 // Tradeskill critical fail chance
WorldTradeskillEventChance RuleType = 25 // Tradeskill event chance
WorldEditorURL RuleType = 26 // Editor URL
WorldEditorIncludeID RuleType = 27 // Editor include ID
WorldEditorOfficialServer RuleType = 28 // Editor official server
WorldSavePaperdollImage RuleType = 29 // Save paperdoll image
WorldSaveHeadshotImage RuleType = 30 // Save headshot image
WorldSendPaperdollImagesToLogin RuleType = 31 // Send paperdoll images to login
WorldTreasureChestDisabled RuleType = 32 // Treasure chest disabled
WorldStartingZoneLanguages RuleType = 33 // Starting zone languages
WorldStartingZoneRuleFlag RuleType = 34 // Starting zone rule flag
WorldEnforceRacialAlignment RuleType = 35 // Enforce racial alignment
WorldMemoryCacheZoneMaps RuleType = 36 // Memory cache zone maps
WorldAutoLockEncounter RuleType = 37 // Auto lock encounter
WorldDisplayItemTiers RuleType = 38 // Display item tiers
WorldLoreAndLegendAccept RuleType = 39 // Lore and legend accept
WorldPlayerCampTimer RuleType = 13 // Player camp timer
WorldGMCampTimer RuleType = 14 // GM camp timer
WorldAutoAdminPlayers RuleType = 15 // Auto admin players
WorldAutoAdminGMs RuleType = 16 // Auto admin GMs
WorldAutoAdminStatusValue RuleType = 17 // Auto admin status value
WorldDuskTime RuleType = 18 // Dusk time
WorldDawnTime RuleType = 19 // Dawn time
WorldThreadedLoad RuleType = 20 // Threaded loading
WorldTradeskillSuccessChance RuleType = 21 // Tradeskill success chance
WorldTradeskillCritSuccessChance RuleType = 22 // Tradeskill critical success chance
WorldTradeskillFailChance RuleType = 23 // Tradeskill fail chance
WorldTradeskillCritFailChance RuleType = 24 // Tradeskill critical fail chance
WorldTradeskillEventChance RuleType = 25 // Tradeskill event chance
WorldEditorURL RuleType = 26 // Editor URL
WorldEditorIncludeID RuleType = 27 // Editor include ID
WorldEditorOfficialServer RuleType = 28 // Editor official server
WorldSavePaperdollImage RuleType = 29 // Save paperdoll image
WorldSaveHeadshotImage RuleType = 30 // Save headshot image
WorldSendPaperdollImagesToLogin RuleType = 31 // Send paperdoll images to login
WorldTreasureChestDisabled RuleType = 32 // Treasure chest disabled
WorldStartingZoneLanguages RuleType = 33 // Starting zone languages
WorldStartingZoneRuleFlag RuleType = 34 // Starting zone rule flag
WorldEnforceRacialAlignment RuleType = 35 // Enforce racial alignment
WorldMemoryCacheZoneMaps RuleType = 36 // Memory cache zone maps
WorldAutoLockEncounter RuleType = 37 // Auto lock encounter
WorldDisplayItemTiers RuleType = 38 // Display item tiers
WorldLoreAndLegendAccept RuleType = 39 // Lore and legend accept
)
// ZONE RULES
const (
ZoneMinLevelOverrideStatus RuleType = 0 // Min level override status
ZoneMinAccessOverrideStatus RuleType = 1 // Min access override status
ZoneXPMultiplier RuleType = 2 // Experience multiplier
ZoneTSXPMultiplier RuleType = 3 // Tradeskill experience multiplier
ZoneWeatherEnabled RuleType = 4 // Weather enabled
ZoneWeatherType RuleType = 5 // Weather type
ZoneMinWeatherSeverity RuleType = 6 // Min weather severity
ZoneMaxWeatherSeverity RuleType = 7 // Max weather severity
ZoneWeatherChangeFrequency RuleType = 8 // Weather change frequency
ZoneWeatherChangePerInterval RuleType = 9 // Weather change per interval
ZoneWeatherDynamicMaxOffset RuleType = 10 // Weather dynamic max offset
ZoneWeatherChangeChance RuleType = 11 // Weather change chance
ZoneSpawnUpdateTimer RuleType = 12 // Spawn update timer
ZoneCheckAttackPlayer RuleType = 13 // Check attack player
ZoneCheckAttackNPC RuleType = 14 // Check attack NPC
ZoneHOTime RuleType = 15 // Heroic opportunity time
ZoneUseMapUnderworldCoords RuleType = 16 // Use map underworld coords
ZoneMapUnderworldCoordOffset RuleType = 17 // Map underworld coord offset
ZoneSharedMaxPlayers RuleType = 18 // Shared zone max players
ZoneRegenTimer RuleType = 19 // Regeneration timer
ZoneClientSaveTimer RuleType = 20 // Client save timer
ZoneShutdownDelayTimer RuleType = 21 // Shutdown delay timer
ZoneWeatherTimer RuleType = 22 // Weather timer
ZoneSpawnDeleteTimer RuleType = 23 // Spawn delete timer
ZoneMinLevelOverrideStatus RuleType = 0 // Min level override status
ZoneMinAccessOverrideStatus RuleType = 1 // Min access override status
ZoneXPMultiplier RuleType = 2 // Experience multiplier
ZoneTSXPMultiplier RuleType = 3 // Tradeskill experience multiplier
ZoneWeatherEnabled RuleType = 4 // Weather enabled
ZoneWeatherType RuleType = 5 // Weather type
ZoneMinWeatherSeverity RuleType = 6 // Min weather severity
ZoneMaxWeatherSeverity RuleType = 7 // Max weather severity
ZoneWeatherChangeFrequency RuleType = 8 // Weather change frequency
ZoneWeatherChangePerInterval RuleType = 9 // Weather change per interval
ZoneWeatherDynamicMaxOffset RuleType = 10 // Weather dynamic max offset
ZoneWeatherChangeChance RuleType = 11 // Weather change chance
ZoneSpawnUpdateTimer RuleType = 12 // Spawn update timer
ZoneCheckAttackPlayer RuleType = 13 // Check attack player
ZoneCheckAttackNPC RuleType = 14 // Check attack NPC
ZoneHOTime RuleType = 15 // Heroic opportunity time
ZoneUseMapUnderworldCoords RuleType = 16 // Use map underworld coords
ZoneMapUnderworldCoordOffset RuleType = 17 // Map underworld coord offset
ZoneSharedMaxPlayers RuleType = 18 // Shared zone max players
ZoneRegenTimer RuleType = 19 // Regeneration timer
ZoneClientSaveTimer RuleType = 20 // Client save timer
ZoneShutdownDelayTimer RuleType = 21 // Shutdown delay timer
ZoneWeatherTimer RuleType = 22 // Weather timer
ZoneSpawnDeleteTimer RuleType = 23 // Spawn delete timer
)
// LOOT RULES
const (
LootRadius RuleType = 0 // Loot pickup radius
LootAutoDisarmChest RuleType = 1 // Auto disarm chest
LootChestTriggerRadiusGroup RuleType = 2 // Chest trigger radius group
LootChestUnlockedTimeDrop RuleType = 3 // Chest unlocked time drop
LootRadius RuleType = 0 // Loot pickup radius
LootAutoDisarmChest RuleType = 1 // Auto disarm chest
LootChestTriggerRadiusGroup RuleType = 2 // Chest trigger radius group
LootChestUnlockedTimeDrop RuleType = 3 // Chest unlocked time drop
LootAllowChestUnlockByDropTime RuleType = 4 // Allow chest unlock by drop time
LootChestUnlockedTimeTrap RuleType = 5 // Chest unlocked time trap
LootChestUnlockedTimeTrap RuleType = 5 // Chest unlocked time trap
LootAllowChestUnlockByTrapTime RuleType = 6 // Allow chest unlock by trap time
LootSkipGrayMob RuleType = 7 // Skip loot from gray mobs
LootDistributionTime RuleType = 8 // Loot distribution time
LootSkipGrayMob RuleType = 7 // Skip loot from gray mobs
LootDistributionTime RuleType = 8 // Loot distribution time
)
// SPELLS RULES
const (
SpellsNoInterruptBaseChance RuleType = 0 // No interrupt base chance
SpellsEnableFizzleSpells RuleType = 1 // Enable fizzle spells
SpellsDefaultFizzleChance RuleType = 2 // Default fizzle chance
SpellsFizzleMaxSkill RuleType = 3 // Fizzle max skill
SpellsFizzleDefaultSkill RuleType = 4 // Fizzle default skill
SpellsEnableCrossZoneGroupBuffs RuleType = 5 // Enable cross zone group buffs
SpellsEnableCrossZoneTargetBuffs RuleType = 6 // Enable cross zone target buffs
SpellsNoInterruptBaseChance RuleType = 0 // No interrupt base chance
SpellsEnableFizzleSpells RuleType = 1 // Enable fizzle spells
SpellsDefaultFizzleChance RuleType = 2 // Default fizzle chance
SpellsFizzleMaxSkill RuleType = 3 // Fizzle max skill
SpellsFizzleDefaultSkill RuleType = 4 // Fizzle default skill
SpellsEnableCrossZoneGroupBuffs RuleType = 5 // Enable cross zone group buffs
SpellsEnableCrossZoneTargetBuffs RuleType = 6 // Enable cross zone target buffs
SpellsPlayerSpellSaveStateWaitInterval RuleType = 7 // Player spell save state wait interval
SpellsPlayerSpellSaveStateCap RuleType = 8 // Player spell save state cap
SpellsRequirePreviousTierScribe RuleType = 9 // Require previous tier scribe
SpellsCureSpellID RuleType = 10 // Cure spell ID
SpellsCureCurseSpellID RuleType = 11 // Cure curse spell ID
SpellsCureNoxiousSpellID RuleType = 12 // Cure noxious spell ID
SpellsCureMagicSpellID RuleType = 13 // Cure magic spell ID
SpellsCureTraumaSpellID RuleType = 14 // Cure trauma spell ID
SpellsCureArcaneSpellID RuleType = 15 // Cure arcane spell ID
SpellsMinistrationSkillID RuleType = 16 // Ministration skill ID
SpellsMinistrationPowerReductionMax RuleType = 17 // Ministration power reduction max
SpellsMinistrationPowerReductionSkill RuleType = 18 // Ministration power reduction skill
SpellsMasterSkillReduceSpellResist RuleType = 19 // Master skill reduce spell resist
SpellsUseClassicSpellLevel RuleType = 20 // Use classic spell level
SpellsPlayerSpellSaveStateCap RuleType = 8 // Player spell save state cap
SpellsRequirePreviousTierScribe RuleType = 9 // Require previous tier scribe
SpellsCureSpellID RuleType = 10 // Cure spell ID
SpellsCureCurseSpellID RuleType = 11 // Cure curse spell ID
SpellsCureNoxiousSpellID RuleType = 12 // Cure noxious spell ID
SpellsCureMagicSpellID RuleType = 13 // Cure magic spell ID
SpellsCureTraumaSpellID RuleType = 14 // Cure trauma spell ID
SpellsCureArcaneSpellID RuleType = 15 // Cure arcane spell ID
SpellsMinistrationSkillID RuleType = 16 // Ministration skill ID
SpellsMinistrationPowerReductionMax RuleType = 17 // Ministration power reduction max
SpellsMinistrationPowerReductionSkill RuleType = 18 // Ministration power reduction skill
SpellsMasterSkillReduceSpellResist RuleType = 19 // Master skill reduce spell resist
SpellsUseClassicSpellLevel RuleType = 20 // Use classic spell level
)
// EXPANSION RULES
const (
ExpansionGlobalFlag RuleType = 0 // Global expansion flag
ExpansionGlobalFlag RuleType = 0 // Global expansion flag
ExpansionHolidayFlag RuleType = 1 // Global holiday flag
)
// DISCORD RULES
const (
DiscordEnabled RuleType = 0 // Discord enabled
DiscordWebhookURL RuleType = 1 // Discord webhook URL
DiscordBotToken RuleType = 2 // Discord bot token
DiscordChannel RuleType = 3 // Discord channel
DiscordListenChan RuleType = 4 // Discord listen channel
DiscordEnabled RuleType = 0 // Discord enabled
DiscordWebhookURL RuleType = 1 // Discord webhook URL
DiscordBotToken RuleType = 2 // Discord bot token
DiscordChannel RuleType = 3 // Discord channel
DiscordListenChan RuleType = 4 // Discord listen channel
)
// Rule validation constants
const (
MaxRuleValueLength = 1024 // Maximum rule value length
MaxRuleValueLength = 1024 // Maximum rule value length
MaxRuleCombinedLength = 2048 // Maximum combined rule string length
MaxRuleNameLength = 64 // Maximum rule name length
MaxRuleNameLength = 64 // Maximum rule name length
)
// Database constants
const (
TableRuleSets = "rulesets"
TableRuleSetDetails = "ruleset_details"
TableVariables = "variables"
DefaultRuleSetIDVar = "default_ruleset_id"
TableRuleSets = "rulesets"
TableRuleSetDetails = "ruleset_details"
TableVariables = "variables"
DefaultRuleSetIDVar = "default_ruleset_id"
)
// Error variables
var (
ErrRuleNotFound = errors.New("rule not found")
ErrRuleSetNotFound = errors.New("rule set not found")
ErrInvalidRuleCategory = errors.New("invalid rule category")
ErrInvalidRuleType = errors.New("invalid rule type")
ErrInvalidRuleValue = errors.New("invalid rule value")
ErrDuplicateRuleSet = errors.New("duplicate rule set")
ErrRuleSetNotActive = errors.New("rule set not active")
ErrGlobalRuleSetNotSet = errors.New("global rule set not set")
ErrZoneRuleSetNotFound = errors.New("zone rule set not found")
ErrRuleValueTooLong = errors.New("rule value too long")
ErrRuleNameTooLong = errors.New("rule name too long")
ErrRuleNotFound = errors.New("rule not found")
ErrRuleSetNotFound = errors.New("rule set not found")
ErrInvalidRuleCategory = errors.New("invalid rule category")
ErrInvalidRuleType = errors.New("invalid rule type")
ErrInvalidRuleValue = errors.New("invalid rule value")
ErrDuplicateRuleSet = errors.New("duplicate rule set")
ErrRuleSetNotActive = errors.New("rule set not active")
ErrGlobalRuleSetNotSet = errors.New("global rule set not set")
ErrZoneRuleSetNotFound = errors.New("zone rule set not found")
ErrRuleValueTooLong = errors.New("rule value too long")
ErrRuleNameTooLong = errors.New("rule name too long")
)
// Rule category names for string conversion

View File

@ -126,12 +126,12 @@ type RuleCache interface {
// CacheStats contains cache performance statistics
type CacheStats struct {
Hits int64 // Number of cache hits
Misses int64 // Number of cache misses
Entries int64 // Number of cached entries
Memory int64 // Memory usage in bytes
Hits int64 // Number of cache hits
Misses int64 // Number of cache misses
Entries int64 // Number of cached entries
Memory int64 // Memory usage in bytes
HitRatio float64 // Cache hit ratio
LastCleared int64 // Timestamp of last cache clear
LastCleared int64 // Timestamp of last cache clear
}
// RuleManagerAdapter provides a simplified interface to the rule manager
@ -219,10 +219,10 @@ func (rma *RuleManagerAdapter) GetZoneID() int32 {
// RuleServiceConfig contains configuration for rule services
type RuleServiceConfig struct {
DatabaseEnabled bool // Whether to use database storage
CacheEnabled bool // Whether to enable rule caching
CacheTTL int64 // Cache TTL in seconds
MaxCacheSize int64 // Maximum cache size in bytes
DatabaseEnabled bool // Whether to use database storage
CacheEnabled bool // Whether to enable rule caching
CacheTTL int64 // Cache TTL in seconds
MaxCacheSize int64 // Maximum cache size in bytes
EventHandlers []RuleEventHandler // Event handlers to register
Validators []RuleValidator // Validators to register
}

View File

@ -10,11 +10,11 @@ import (
// Converted from C++ RuleManager class
type RuleManager struct {
// Core rule storage
rules map[RuleCategory]map[RuleType]*Rule // Default rules from code
ruleSets map[int32]*RuleSet // Rule sets loaded from database
globalRuleSet *RuleSet // Active global rule set
zoneRuleSets map[int32]*RuleSet // Zone-specific rule sets
blankRule *Rule // Default blank rule
rules map[RuleCategory]map[RuleType]*Rule // Default rules from code
ruleSets map[int32]*RuleSet // Rule sets loaded from database
globalRuleSet *RuleSet // Active global rule set
zoneRuleSets map[int32]*RuleSet // Zone-specific rule sets
blankRule *Rule // Default blank rule
// Thread safety
rulesMutex sync.RWMutex

View File

@ -8,10 +8,10 @@ const (
// Default spawn settings for signs
const (
DefaultSpawnType = 2 // Signs are spawn type 2
DefaultActivityStatus = 64 // Activity status for signs
DefaultPosState = 1 // Position state
DefaultDifficulty = 0 // No difficulty for signs
DefaultSpawnType = 2 // Signs are spawn type 2
DefaultActivityStatus = 64 // Activity status for signs
DefaultPosState = 1 // Position state
DefaultDifficulty = 0 // No difficulty for signs
)
// Channel colors for messages (these would be defined elsewhere in a real implementation)

View File

@ -7,30 +7,30 @@ import (
// Manager provides high-level management of the sign system
type Manager struct {
signs map[int32]*Sign // Signs by ID
signsByZone map[int32][]*Sign // Signs by zone ID
signsByWidget map[int32]*Sign // Signs by widget ID
database Database
logger Logger
mutex sync.RWMutex
signs map[int32]*Sign // Signs by ID
signsByZone map[int32][]*Sign // Signs by zone ID
signsByWidget map[int32]*Sign // Signs by widget ID
database Database
logger Logger
mutex sync.RWMutex
// Statistics
totalSigns int64
signsByType map[int8]int64 // Sign type -> count
signInteractions int64
zoneTransports int64
transporterUses int64
totalSigns int64
signsByType map[int8]int64 // Sign type -> count
signInteractions int64
zoneTransports int64
transporterUses int64
}
// NewManager creates a new sign manager
func NewManager(database Database, logger Logger) *Manager {
return &Manager{
signs: make(map[int32]*Sign),
signsByZone: make(map[int32][]*Sign),
signsByWidget: make(map[int32]*Sign),
database: database,
logger: logger,
signsByType: make(map[int8]int64),
signs: make(map[int32]*Sign),
signsByZone: make(map[int32][]*Sign),
signsByWidget: make(map[int32]*Sign),
database: database,
logger: logger,
signsByType: make(map[int8]int64),
}
}

View File

@ -4,7 +4,6 @@ import (
"fmt"
"math/rand"
"strings"
"eq2emu/internal/spawn"
)
// Copy creates a deep copy of the sign with size randomization

Some files were not shown because too many files have changed in this diff Show More