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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -985,8 +985,8 @@ func (dhm *DatabaseHousingManager) GetHouseStatistics(ctx context.Context) (*Hou
// Get basic counts // Get basic counts
queries := map[string]*int64{ queries := map[string]*int64{
"SELECT COUNT(*) FROM character_houses": &stats.TotalHouses, "SELECT COUNT(*) FROM character_houses": &stats.TotalHouses,
"SELECT COUNT(*) FROM character_houses WHERE status = 0": &stats.ActiveHouses, "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 = 2": &stats.ForelosedHouses,
"SELECT COUNT(*) FROM character_house_deposits": &stats.TotalDeposits, "SELECT COUNT(*) FROM character_house_deposits": &stats.TotalDeposits,
"SELECT COUNT(*) FROM character_house_history WHERE pos_flag = 0": &stats.TotalWithdrawals, "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 // HouseInstance contains active house instance information
type HouseInstance struct { type HouseInstance struct {
InstanceID int32 `json:"instance_id"` InstanceID int32 `json:"instance_id"`
HouseID int64 `json:"house_id"` HouseID int64 `json:"house_id"`
OwnerID int32 `json:"owner_id"` OwnerID int32 `json:"owner_id"`
ZoneID int32 `json:"zone_id"` ZoneID int32 `json:"zone_id"`
CreatedTime time.Time `json:"created_time"` CreatedTime time.Time `json:"created_time"`
LastActivity time.Time `json:"last_activity"` LastActivity time.Time `json:"last_activity"`
CurrentVisitors []int32 `json:"current_visitors"` CurrentVisitors []int32 `json:"current_visitors"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
} }
// Adapter interfaces for integration with existing systems // Adapter interfaces for integration with existing systems

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,36 +2,36 @@ package ai
// AI tick constants // AI tick constants
const ( const (
DefaultThinkTick int32 = 250 // Default think tick in milliseconds (1/4 second) DefaultThinkTick int32 = 250 // Default think tick in milliseconds (1/4 second)
FastThinkTick int32 = 100 // Fast think tick for active AI FastThinkTick int32 = 100 // Fast think tick for active AI
SlowThinkTick int32 = 1000 // Slow think tick for idle AI SlowThinkTick int32 = 1000 // Slow think tick for idle AI
BlankBrainTick int32 = 50000 // Very slow tick for blank brain BlankBrainTick int32 = 50000 // Very slow tick for blank brain
MaxThinkTick int32 = 60000 // Maximum think tick (1 minute) MaxThinkTick int32 = 60000 // Maximum think tick (1 minute)
) )
// Combat constants // Combat constants
const ( const (
MaxChaseDistance float32 = 150.0 // Default max chase distance MaxChaseDistance float32 = 150.0 // Default max chase distance
MaxCombatRange float32 = 25.0 // Default max combat range MaxCombatRange float32 = 25.0 // Default max combat range
RunbackThreshold float32 = 1.0 // Distance threshold for runback RunbackThreshold float32 = 1.0 // Distance threshold for runback
) )
// Hate system constants // Hate system constants
const ( const (
MinHateValue int32 = 1 // Minimum hate value (0 or negative is invalid) MinHateValue int32 = 1 // Minimum hate value (0 or negative is invalid)
MaxHateValue int32 = 2147483647 // Maximum hate value (INT_MAX) MaxHateValue int32 = 2147483647 // Maximum hate value (INT_MAX)
DefaultHateValue int32 = 100 // Default hate amount DefaultHateValue int32 = 100 // Default hate amount
MaxHateListSize int = 100 // Maximum entities in hate list MaxHateListSize int = 100 // Maximum entities in hate list
) )
// Encounter system constants // Encounter system constants
const ( const (
MaxEncounterSize int = 50 // Maximum entities in encounter list MaxEncounterSize int = 50 // Maximum entities in encounter list
) )
// Spell recovery constants // Spell recovery constants
const ( const (
SpellRecoveryBuffer int32 = 2000 // Additional recovery time buffer (2 seconds) SpellRecoveryBuffer int32 = 2000 // Additional recovery time buffer (2 seconds)
) )
// Brain type constants for identification // Brain type constants for identification
@ -46,9 +46,9 @@ const (
// Pet movement constants // Pet movement constants
const ( const (
PetMovementFollow int8 = 0 PetMovementFollow int8 = 0
PetMovementStay int8 = 1 PetMovementStay int8 = 1
PetMovementGuard int8 = 2 PetMovementGuard int8 = 2
) )
// Encounter state constants // Encounter state constants
@ -60,31 +60,31 @@ const (
// Combat decision constants // Combat decision constants
const ( const (
MeleeAttackChance int = 70 // Base chance for melee attack MeleeAttackChance int = 70 // Base chance for melee attack
SpellCastChance int = 30 // Base chance for spell casting SpellCastChance int = 30 // Base chance for spell casting
BuffCheckChance int = 50 // Chance to check for buffs BuffCheckChance int = 50 // Chance to check for buffs
) )
// AI state flags // AI state flags
const ( const (
AIStateIdle int32 = 0 AIStateIdle int32 = 0
AIStateCombat int32 = 1 AIStateCombat int32 = 1
AIStateFollowing int32 = 2 AIStateFollowing int32 = 2
AIStateRunback int32 = 3 AIStateRunback int32 = 3
AIStateCasting int32 = 4 AIStateCasting int32 = 4
AIStateMoving int32 = 5 AIStateMoving int32 = 5
) )
// Debug levels // Debug levels
const ( const (
DebugLevelNone int8 = 0 DebugLevelNone int8 = 0
DebugLevelBasic int8 = 1 DebugLevelBasic int8 = 1
DebugLevelDetailed int8 = 2 DebugLevelDetailed int8 = 2
DebugLevelVerbose int8 = 3 DebugLevelVerbose int8 = 3
) )
// Timer constants // Timer constants
const ( const (
MillisecondsPerSecond int32 = 1000 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 package ai
import "fmt" import (
"fmt"
"time"
)
// Logger interface for AI logging // Logger interface for AI logging
type Logger interface { type Logger interface {
@ -146,11 +149,11 @@ type Zone interface {
// AIManager provides high-level management of the AI system // AIManager provides high-level management of the AI system
type AIManager struct { type AIManager struct {
brains map[int32]Brain // Map of NPC ID to brain brains map[int32]Brain // Map of NPC ID to brain
activeCount int64 // Number of active brains activeCount int64 // Number of active brains
totalThinks int64 // Total think cycles processed totalThinks int64 // Total think cycles processed
logger Logger // Logger for AI events logger Logger // Logger for AI events
luaInterface LuaInterface // Lua script interface luaInterface LuaInterface // Lua script interface
} }
// NewAIManager creates a new AI manager // NewAIManager creates a new AI manager
@ -325,10 +328,10 @@ func (am *AIManager) ClearAllBrains() {
// GetStatistics returns overall AI system statistics // GetStatistics returns overall AI system statistics
func (am *AIManager) GetStatistics() *AIStatistics { func (am *AIManager) GetStatistics() *AIStatistics {
return &AIStatistics{ return &AIStatistics{
TotalBrains: len(am.brains), TotalBrains: len(am.brains),
ActiveBrains: int(am.activeCount), ActiveBrains: int(am.activeCount),
TotalThinks: am.totalThinks, TotalThinks: am.totalThinks,
BrainsByType: am.getBrainCountsByType(), BrainsByType: am.getBrainCountsByType(),
} }
} }
@ -346,10 +349,10 @@ func (am *AIManager) getBrainCountsByType() map[string]int {
// AIStatistics contains AI system statistics // AIStatistics contains AI system statistics
type AIStatistics struct { type AIStatistics struct {
TotalBrains int `json:"total_brains"` TotalBrains int `json:"total_brains"`
ActiveBrains int `json:"active_brains"` ActiveBrains int `json:"active_brains"`
TotalThinks int64 `json:"total_thinks"` TotalThinks int64 `json:"total_thinks"`
BrainsByType map[string]int `json:"brains_by_type"` BrainsByType map[string]int `json:"brains_by_type"`
} }
// AIBrainAdapter provides NPC functionality for brains // AIBrainAdapter provides NPC functionality for brains

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"eq2emu/internal/spawn" "eq2emu/internal/spawn"
"eq2emu/internal/common"
) )
// ObjectSpawn represents an object that extends spawn functionality // ObjectSpawn represents an object that extends spawn functionality
@ -13,8 +12,8 @@ type ObjectSpawn struct {
*spawn.Spawn // Embed the spawn functionality *spawn.Spawn // Embed the spawn functionality
// Object-specific properties // Object-specific properties
clickable bool // Whether the object can be clicked/interacted with clickable bool // Whether the object can be clicked/interacted with
deviceID int8 // Device ID for interactive objects deviceID int8 // Device ID for interactive objects
} }
// NewObjectSpawn creates a new object spawn with default values // 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 // ObjectSpawnManager manages object spawns specifically
type ObjectSpawnManager struct { 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 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.) // ObjectItem represents an item provided by an object (merchants, containers, etc.)
type ObjectItem struct { type ObjectItem struct {
id int32 id int32
name string name string
quantity int32 quantity int32
iconID int32 iconID int32
noTrade bool noTrade bool
heirloom bool heirloom bool
attuned bool attuned bool
creationTime time.Time creationTime time.Time
groupCharacterIDs []int32 groupCharacterIDs []int32
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,10 @@
package quests package quests
import (
"fmt"
"strings"
)
// Action management methods for Quest // Action management methods for Quest
// AddCompleteAction adds a completion action for a step // 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 // Check for obviously invalid syntax patterns
invalidPatterns := []string{ invalidPatterns := []string{
"--[[", // Unfinished multi-line comments "--[[", // Unfinished multi-line comments
"function(", // Function without closing "function(", // Function without closing
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,10 +8,10 @@ const (
// Default spawn settings for signs // Default spawn settings for signs
const ( const (
DefaultSpawnType = 2 // Signs are spawn type 2 DefaultSpawnType = 2 // Signs are spawn type 2
DefaultActivityStatus = 64 // Activity status for signs DefaultActivityStatus = 64 // Activity status for signs
DefaultPosState = 1 // Position state DefaultPosState = 1 // Position state
DefaultDifficulty = 0 // No difficulty for signs DefaultDifficulty = 0 // No difficulty for signs
) )
// Channel colors for messages (these would be defined elsewhere in a real implementation) // 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 // Manager provides high-level management of the sign system
type Manager struct { type Manager struct {
signs map[int32]*Sign // Signs by ID signs map[int32]*Sign // Signs by ID
signsByZone map[int32][]*Sign // Signs by zone ID signsByZone map[int32][]*Sign // Signs by zone ID
signsByWidget map[int32]*Sign // Signs by widget ID signsByWidget map[int32]*Sign // Signs by widget ID
database Database database Database
logger Logger logger Logger
mutex sync.RWMutex mutex sync.RWMutex
// Statistics // Statistics
totalSigns int64 totalSigns int64
signsByType map[int8]int64 // Sign type -> count signsByType map[int8]int64 // Sign type -> count
signInteractions int64 signInteractions int64
zoneTransports int64 zoneTransports int64
transporterUses int64 transporterUses int64
} }
// NewManager creates a new sign manager // NewManager creates a new sign manager
func NewManager(database Database, logger Logger) *Manager { func NewManager(database Database, logger Logger) *Manager {
return &Manager{ return &Manager{
signs: make(map[int32]*Sign), signs: make(map[int32]*Sign),
signsByZone: make(map[int32][]*Sign), signsByZone: make(map[int32][]*Sign),
signsByWidget: make(map[int32]*Sign), signsByWidget: make(map[int32]*Sign),
database: database, database: database,
logger: logger, logger: logger,
signsByType: make(map[int8]int64), signsByType: make(map[int8]int64),
} }
} }

View File

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