convert more internals
This commit is contained in:
parent
47e6102af1
commit
812dd6716a
5261
internal/Items.cpp
Normal file
5261
internal/Items.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1298
internal/Items.h
Normal file
1298
internal/Items.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,16 +2,16 @@ package alt_advancement
|
||||
|
||||
// AA tab/group constants based on group # from DB
|
||||
const (
|
||||
AA_CLASS = 0 // Class-specific advancement trees
|
||||
AA_SUBCLASS = 1 // Subclass-specific advancement trees
|
||||
AA_SHADOW = 2 // Shadows advancement (from Shadows of Luclin)
|
||||
AA_HEROIC = 3 // Heroic advancement (from Destiny of Velious)
|
||||
AA_TRADESKILL = 4 // Tradeskill advancement trees
|
||||
AA_PRESTIGE = 5 // Prestige advancement (from Destiny of Velious)
|
||||
AA_TRADESKILL_PRESTIGE = 6 // Tradeskill prestige advancement
|
||||
AA_DRAGON = 7 // Dragon advancement
|
||||
AA_DRAGONCLASS = 8 // Dragon class-specific advancement
|
||||
AA_FARSEAS = 9 // Far Seas advancement
|
||||
AA_CLASS = 0 // Class-specific advancement trees
|
||||
AA_SUBCLASS = 1 // Subclass-specific advancement trees
|
||||
AA_SHADOW = 2 // Shadows advancement (from Shadows of Luclin)
|
||||
AA_HEROIC = 3 // Heroic advancement (from Destiny of Velious)
|
||||
AA_TRADESKILL = 4 // Tradeskill advancement trees
|
||||
AA_PRESTIGE = 5 // Prestige advancement (from Destiny of Velious)
|
||||
AA_TRADESKILL_PRESTIGE = 6 // Tradeskill prestige advancement
|
||||
AA_DRAGON = 7 // Dragon advancement
|
||||
AA_DRAGONCLASS = 8 // Dragon class-specific advancement
|
||||
AA_FARSEAS = 9 // Far Seas advancement
|
||||
)
|
||||
|
||||
// AA tab names for display
|
||||
@ -30,28 +30,28 @@ var AATabNames = map[int8]string{
|
||||
|
||||
// Maximum AA values per tab (from C++ packet data)
|
||||
const (
|
||||
MAX_CLASS_AA = 100 // 0x64
|
||||
MAX_SUBCLASS_AA = 100 // 0x64
|
||||
MAX_SHADOWS_AA = 70 // 0x46
|
||||
MAX_HEROIC_AA = 50 // 0x32
|
||||
MAX_TRADESKILL_AA = 40 // 0x28
|
||||
MAX_PRESTIGE_AA = 25 // 0x19
|
||||
MAX_TRADESKILL_PRESTIGE_AA = 25 // 0x19
|
||||
MAX_DRAGON_AA = 100 // Estimated
|
||||
MAX_DRAGONCLASS_AA = 100 // Estimated
|
||||
MAX_FARSEAS_AA = 100 // Estimated
|
||||
MAX_CLASS_AA = 100 // 0x64
|
||||
MAX_SUBCLASS_AA = 100 // 0x64
|
||||
MAX_SHADOWS_AA = 70 // 0x46
|
||||
MAX_HEROIC_AA = 50 // 0x32
|
||||
MAX_TRADESKILL_AA = 40 // 0x28
|
||||
MAX_PRESTIGE_AA = 25 // 0x19
|
||||
MAX_TRADESKILL_PRESTIGE_AA = 25 // 0x19
|
||||
MAX_DRAGON_AA = 100 // Estimated
|
||||
MAX_DRAGONCLASS_AA = 100 // Estimated
|
||||
MAX_FARSEAS_AA = 100 // Estimated
|
||||
)
|
||||
|
||||
// AA template constants
|
||||
const (
|
||||
AA_TEMPLATE_PERSONAL_1 = 1 // Personal template 1
|
||||
AA_TEMPLATE_PERSONAL_2 = 2 // Personal template 2
|
||||
AA_TEMPLATE_PERSONAL_3 = 3 // Personal template 3
|
||||
AA_TEMPLATE_SERVER_1 = 4 // Server template 1
|
||||
AA_TEMPLATE_SERVER_2 = 5 // Server template 2
|
||||
AA_TEMPLATE_SERVER_3 = 6 // Server template 3
|
||||
AA_TEMPLATE_CURRENT = 7 // Current active template
|
||||
MAX_AA_TEMPLATES = 8 // Maximum number of templates
|
||||
AA_TEMPLATE_PERSONAL_1 = 1 // Personal template 1
|
||||
AA_TEMPLATE_PERSONAL_2 = 2 // Personal template 2
|
||||
AA_TEMPLATE_PERSONAL_3 = 3 // Personal template 3
|
||||
AA_TEMPLATE_SERVER_1 = 4 // Server template 1
|
||||
AA_TEMPLATE_SERVER_2 = 5 // Server template 2
|
||||
AA_TEMPLATE_SERVER_3 = 6 // Server template 3
|
||||
AA_TEMPLATE_CURRENT = 7 // Current active template
|
||||
MAX_AA_TEMPLATES = 8 // Maximum number of templates
|
||||
)
|
||||
|
||||
// AA template names
|
||||
@ -67,98 +67,98 @@ var AATemplateNames = map[int8]string{
|
||||
|
||||
// AA prerequisite constants
|
||||
const (
|
||||
AA_PREREQ_NONE = 0 // No prerequisite
|
||||
AA_PREREQ_EXPANSION = 1 // Requires specific expansion
|
||||
AA_PREREQ_LEVEL = 2 // Requires minimum level
|
||||
AA_PREREQ_CLASS = 3 // Requires specific class
|
||||
AA_PREREQ_POINTS = 4 // Requires points spent in tree
|
||||
AA_PREREQ_ACHIEVEMENT = 5 // Requires achievement completion
|
||||
AA_PREREQ_NONE = 0 // No prerequisite
|
||||
AA_PREREQ_EXPANSION = 1 // Requires specific expansion
|
||||
AA_PREREQ_LEVEL = 2 // Requires minimum level
|
||||
AA_PREREQ_CLASS = 3 // Requires specific class
|
||||
AA_PREREQ_POINTS = 4 // Requires points spent in tree
|
||||
AA_PREREQ_ACHIEVEMENT = 5 // Requires achievement completion
|
||||
)
|
||||
|
||||
// Expansion requirement flags
|
||||
const (
|
||||
EXPANSION_NONE = 0x00 // No expansion required
|
||||
EXPANSION_KOS = 0x01 // Kingdom of Sky required
|
||||
EXPANSION_EOF = 0x02 // Echoes of Faydwer required
|
||||
EXPANSION_ROK = 0x04 // Rise of Kunark required
|
||||
EXPANSION_TSO = 0x08 // The Shadow Odyssey required
|
||||
EXPANSION_SF = 0x10 // Sentinel's Fate required
|
||||
EXPANSION_DOV = 0x20 // Destiny of Velious required
|
||||
EXPANSION_COE = 0x40 // Chains of Eternity required
|
||||
EXPANSION_TOV = 0x80 // Tears of Veeshan required
|
||||
EXPANSION_NONE = 0x00 // No expansion required
|
||||
EXPANSION_KOS = 0x01 // Kingdom of Sky required
|
||||
EXPANSION_EOF = 0x02 // Echoes of Faydwer required
|
||||
EXPANSION_ROK = 0x04 // Rise of Kunark required
|
||||
EXPANSION_TSO = 0x08 // The Shadow Odyssey required
|
||||
EXPANSION_SF = 0x10 // Sentinel's Fate required
|
||||
EXPANSION_DOV = 0x20 // Destiny of Velious required
|
||||
EXPANSION_COE = 0x40 // Chains of Eternity required
|
||||
EXPANSION_TOV = 0x80 // Tears of Veeshan required
|
||||
)
|
||||
|
||||
// AA node positioning constants
|
||||
const (
|
||||
MIN_AA_COL = 0 // Minimum column position
|
||||
MAX_AA_COL = 10 // Maximum column position
|
||||
MIN_AA_ROW = 0 // Minimum row position
|
||||
MAX_AA_ROW = 15 // Maximum row position
|
||||
MIN_AA_COL = 0 // Minimum column position
|
||||
MAX_AA_COL = 10 // Maximum column position
|
||||
MIN_AA_ROW = 0 // Minimum row position
|
||||
MAX_AA_ROW = 15 // Maximum row position
|
||||
)
|
||||
|
||||
// AA cost and rank constants
|
||||
const (
|
||||
MIN_RANK_COST = 1 // Minimum cost per rank
|
||||
MAX_RANK_COST = 10 // Maximum cost per rank
|
||||
MIN_MAX_RANK = 1 // Minimum maximum rank
|
||||
MAX_MAX_RANK = 20 // Maximum maximum rank
|
||||
MIN_TITLE_LEVEL = 1 // Minimum title level
|
||||
MAX_TITLE_LEVEL = 100 // Maximum title level
|
||||
MIN_RANK_COST = 1 // Minimum cost per rank
|
||||
MAX_RANK_COST = 10 // Maximum cost per rank
|
||||
MIN_MAX_RANK = 1 // Minimum maximum rank
|
||||
MAX_MAX_RANK = 20 // Maximum maximum rank
|
||||
MIN_TITLE_LEVEL = 1 // Minimum title level
|
||||
MAX_TITLE_LEVEL = 100 // Maximum title level
|
||||
)
|
||||
|
||||
// AA packet operation codes
|
||||
const (
|
||||
OP_ADVENTURE_LIST = 0x023B // Adventure list packet opcode
|
||||
OP_AA_UPDATE = 0x024C // AA update packet opcode
|
||||
OP_AA_PURCHASE = 0x024D // AA purchase packet opcode
|
||||
OP_ADVENTURE_LIST = 0x023B // Adventure list packet opcode
|
||||
OP_AA_UPDATE = 0x024C // AA update packet opcode
|
||||
OP_AA_PURCHASE = 0x024D // AA purchase packet opcode
|
||||
)
|
||||
|
||||
// AA display modes
|
||||
const (
|
||||
AA_DISPLAY_NEW = 0 // New template display
|
||||
AA_DISPLAY_CHANGE = 1 // Change template display
|
||||
AA_DISPLAY_UPDATE = 2 // Update existing display
|
||||
AA_DISPLAY_NEW = 0 // New template display
|
||||
AA_DISPLAY_CHANGE = 1 // Change template display
|
||||
AA_DISPLAY_UPDATE = 2 // Update existing display
|
||||
)
|
||||
|
||||
// AA validation constants
|
||||
const (
|
||||
MIN_SPELL_ID = 1 // Minimum valid spell ID
|
||||
MAX_SPELL_ID = 2147483647 // Maximum valid spell ID
|
||||
MIN_NODE_ID = 1 // Minimum valid node ID
|
||||
MAX_NODE_ID = 2147483647 // Maximum valid node ID
|
||||
MIN_SPELL_ID = 1 // Minimum valid spell ID
|
||||
MAX_SPELL_ID = 2147483647 // Maximum valid spell ID
|
||||
MIN_NODE_ID = 1 // Minimum valid node ID
|
||||
MAX_NODE_ID = 2147483647 // Maximum valid node ID
|
||||
)
|
||||
|
||||
// AA processing constants
|
||||
const (
|
||||
AA_PROCESSING_BATCH_SIZE = 100 // Batch size for processing AAs
|
||||
AA_CACHE_SIZE = 10000 // Cache size for AA data
|
||||
AA_UPDATE_INTERVAL = 1000 // Update interval in milliseconds
|
||||
AA_PROCESSING_BATCH_SIZE = 100 // Batch size for processing AAs
|
||||
AA_CACHE_SIZE = 10000 // Cache size for AA data
|
||||
AA_UPDATE_INTERVAL = 1000 // Update interval in milliseconds
|
||||
)
|
||||
|
||||
// AA error codes
|
||||
const (
|
||||
AA_ERROR_NONE = 0 // No error
|
||||
AA_ERROR_INVALID_SPELL_ID = 1 // Invalid spell ID
|
||||
AA_ERROR_INVALID_NODE_ID = 2 // Invalid node ID
|
||||
AA_ERROR_INSUFFICIENT_POINTS = 3 // Insufficient AA points
|
||||
AA_ERROR_PREREQ_NOT_MET = 4 // Prerequisites not met
|
||||
AA_ERROR_MAX_RANK_REACHED = 5 // Maximum rank already reached
|
||||
AA_ERROR_INVALID_CLASS = 6 // Invalid class for this AA
|
||||
AA_ERROR_EXPANSION_REQUIRED = 7 // Required expansion not owned
|
||||
AA_ERROR_LEVEL_TOO_LOW = 8 // Character level too low
|
||||
AA_ERROR_TREE_LOCKED = 9 // AA tree is locked
|
||||
AA_ERROR_DATABASE_ERROR = 10 // Database operation failed
|
||||
AA_ERROR_NONE = 0 // No error
|
||||
AA_ERROR_INVALID_SPELL_ID = 1 // Invalid spell ID
|
||||
AA_ERROR_INVALID_NODE_ID = 2 // Invalid node ID
|
||||
AA_ERROR_INSUFFICIENT_POINTS = 3 // Insufficient AA points
|
||||
AA_ERROR_PREREQ_NOT_MET = 4 // Prerequisites not met
|
||||
AA_ERROR_MAX_RANK_REACHED = 5 // Maximum rank already reached
|
||||
AA_ERROR_INVALID_CLASS = 6 // Invalid class for this AA
|
||||
AA_ERROR_EXPANSION_REQUIRED = 7 // Required expansion not owned
|
||||
AA_ERROR_LEVEL_TOO_LOW = 8 // Character level too low
|
||||
AA_ERROR_TREE_LOCKED = 9 // AA tree is locked
|
||||
AA_ERROR_DATABASE_ERROR = 10 // Database operation failed
|
||||
)
|
||||
|
||||
// AA statistic tracking constants
|
||||
const (
|
||||
STAT_TOTAL_AAS_LOADED = "total_aas_loaded"
|
||||
STAT_TOTAL_NODES_LOADED = "total_nodes_loaded"
|
||||
STAT_AAS_PER_TAB = "aas_per_tab"
|
||||
STAT_PLAYER_AA_PURCHASES = "player_aa_purchases"
|
||||
STAT_CACHE_HITS = "cache_hits"
|
||||
STAT_CACHE_MISSES = "cache_misses"
|
||||
STAT_DATABASE_QUERIES = "database_queries"
|
||||
STAT_TOTAL_AAS_LOADED = "total_aas_loaded"
|
||||
STAT_TOTAL_NODES_LOADED = "total_nodes_loaded"
|
||||
STAT_AAS_PER_TAB = "aas_per_tab"
|
||||
STAT_PLAYER_AA_PURCHASES = "player_aa_purchases"
|
||||
STAT_CACHE_HITS = "cache_hits"
|
||||
STAT_CACHE_MISSES = "cache_misses"
|
||||
STAT_DATABASE_QUERIES = "database_queries"
|
||||
)
|
||||
|
||||
// Default AA configuration values
|
||||
|
@ -3,6 +3,7 @@ package alt_advancement
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -273,7 +274,7 @@ type AAAware interface {
|
||||
|
||||
// AAAdapter adapts AA functionality for other systems
|
||||
type AAAdapter struct {
|
||||
manager AAManagerInterface
|
||||
manager AAManagerInterface
|
||||
characterID int32
|
||||
}
|
||||
|
||||
@ -583,12 +584,12 @@ func (c *SimpleAACache) GetStats() map[string]interface{} {
|
||||
defer c.mutex.RUnlock()
|
||||
|
||||
return map[string]interface{}{
|
||||
"hits": c.hits,
|
||||
"misses": c.misses,
|
||||
"aa_data_count": len(c.aaData),
|
||||
"player_count": len(c.playerStates),
|
||||
"tree_node_count": len(c.treeNodes),
|
||||
"max_size": c.maxSize,
|
||||
"hits": c.hits,
|
||||
"misses": c.misses,
|
||||
"aa_data_count": len(c.aaData),
|
||||
"player_count": len(c.playerStates),
|
||||
"tree_node_count": len(c.treeNodes),
|
||||
"max_size": c.maxSize,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package alt_advancement
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -534,14 +533,14 @@ func (am *AAManager) performAAPurchase(playerState *AAPlayerState, aaData *AltAd
|
||||
progress := playerState.AAProgress[aaData.NodeID]
|
||||
if progress == nil {
|
||||
progress = &PlayerAAData{
|
||||
CharacterID: playerState.CharacterID,
|
||||
NodeID: aaData.NodeID,
|
||||
CurrentRank: 0,
|
||||
PointsSpent: 0,
|
||||
TemplateID: playerState.ActiveTemplate,
|
||||
TabID: aaData.Group,
|
||||
PurchasedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CharacterID: playerState.CharacterID,
|
||||
NodeID: aaData.NodeID,
|
||||
CurrentRank: 0,
|
||||
PointsSpent: 0,
|
||||
TemplateID: playerState.ActiveTemplate,
|
||||
TabID: aaData.Group,
|
||||
PurchasedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
playerState.AAProgress[aaData.NodeID] = progress
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package alt_advancement
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -8,287 +8,287 @@ import (
|
||||
// AltAdvanceData represents an Alternate Advancement node
|
||||
type AltAdvanceData struct {
|
||||
// Core identification
|
||||
SpellID int32 `json:"spell_id" db:"spell_id"`
|
||||
NodeID int32 `json:"node_id" db:"node_id"`
|
||||
SpellCRC int32 `json:"spell_crc" db:"spell_crc"`
|
||||
SpellID int32 `json:"spell_id" db:"spell_id"`
|
||||
NodeID int32 `json:"node_id" db:"node_id"`
|
||||
SpellCRC int32 `json:"spell_crc" db:"spell_crc"`
|
||||
|
||||
// Display information
|
||||
Name string `json:"name" db:"name"`
|
||||
Description string `json:"description" db:"description"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Description string `json:"description" db:"description"`
|
||||
|
||||
// Tree organization
|
||||
Group int8 `json:"group" db:"group"` // AA tab (AA_CLASS, AA_SUBCLASS, etc.)
|
||||
Col int8 `json:"col" db:"col"` // Column position in tree
|
||||
Row int8 `json:"row" db:"row"` // Row position in tree
|
||||
Group int8 `json:"group" db:"group"` // AA tab (AA_CLASS, AA_SUBCLASS, etc.)
|
||||
Col int8 `json:"col" db:"col"` // Column position in tree
|
||||
Row int8 `json:"row" db:"row"` // Row position in tree
|
||||
|
||||
// Visual representation
|
||||
Icon int16 `json:"icon" db:"icon"` // Primary icon ID
|
||||
Icon2 int16 `json:"icon2" db:"icon2"` // Secondary icon ID
|
||||
Icon int16 `json:"icon" db:"icon"` // Primary icon ID
|
||||
Icon2 int16 `json:"icon2" db:"icon2"` // Secondary icon ID
|
||||
|
||||
// Ranking system
|
||||
RankCost int8 `json:"rank_cost" db:"rank_cost"` // Cost per rank
|
||||
MaxRank int8 `json:"max_rank" db:"max_rank"` // Maximum achievable rank
|
||||
RankCost int8 `json:"rank_cost" db:"rank_cost"` // Cost per rank
|
||||
MaxRank int8 `json:"max_rank" db:"max_rank"` // Maximum achievable rank
|
||||
|
||||
// Prerequisites
|
||||
MinLevel int8 `json:"min_level" db:"min_level"` // Minimum character level
|
||||
RankPrereqID int32 `json:"rank_prereq_id" db:"rank_prereq_id"` // Prerequisite AA node ID
|
||||
RankPrereq int8 `json:"rank_prereq" db:"rank_prereq"` // Required rank in prerequisite
|
||||
ClassReq int8 `json:"class_req" db:"class_req"` // Required class
|
||||
Tier int8 `json:"tier" db:"tier"` // AA tier
|
||||
ReqPoints int8 `json:"req_points" db:"req_points"` // Required points in classification
|
||||
ReqTreePoints int16 `json:"req_tree_points" db:"req_tree_points"` // Required points in entire tree
|
||||
MinLevel int8 `json:"min_level" db:"min_level"` // Minimum character level
|
||||
RankPrereqID int32 `json:"rank_prereq_id" db:"rank_prereq_id"` // Prerequisite AA node ID
|
||||
RankPrereq int8 `json:"rank_prereq" db:"rank_prereq"` // Required rank in prerequisite
|
||||
ClassReq int8 `json:"class_req" db:"class_req"` // Required class
|
||||
Tier int8 `json:"tier" db:"tier"` // AA tier
|
||||
ReqPoints int8 `json:"req_points" db:"req_points"` // Required points in classification
|
||||
ReqTreePoints int16 `json:"req_tree_points" db:"req_tree_points"` // Required points in entire tree
|
||||
|
||||
// Display classification
|
||||
ClassName string `json:"class_name" db:"class_name"` // Class name for display
|
||||
SubclassName string `json:"subclass_name" db:"subclass_name"` // Subclass name for display
|
||||
LineTitle string `json:"line_title" db:"line_title"` // AA line title
|
||||
TitleLevel int8 `json:"title_level" db:"title_level"` // Title level requirement
|
||||
ClassName string `json:"class_name" db:"class_name"` // Class name for display
|
||||
SubclassName string `json:"subclass_name" db:"subclass_name"` // Subclass name for display
|
||||
LineTitle string `json:"line_title" db:"line_title"` // AA line title
|
||||
TitleLevel int8 `json:"title_level" db:"title_level"` // Title level requirement
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// TreeNodeData represents class-specific AA tree node configuration
|
||||
type TreeNodeData struct {
|
||||
ClassID int32 `json:"class_id" db:"class_id"` // Character class ID
|
||||
TreeID int32 `json:"tree_id" db:"tree_id"` // Tree node identifier
|
||||
AATreeID int32 `json:"aa_tree_id" db:"aa_tree_id"` // AA tree classification ID
|
||||
ClassID int32 `json:"class_id" db:"class_id"` // Character class ID
|
||||
TreeID int32 `json:"tree_id" db:"tree_id"` // Tree node identifier
|
||||
AATreeID int32 `json:"aa_tree_id" db:"aa_tree_id"` // AA tree classification ID
|
||||
}
|
||||
|
||||
// AAEntry represents a player's AA entry in a template
|
||||
type AAEntry struct {
|
||||
TemplateID int8 `json:"template_id" db:"template_id"` // Template identifier (1-8)
|
||||
TabID int8 `json:"tab_id" db:"tab_id"` // Tab identifier
|
||||
AAID int32 `json:"aa_id" db:"aa_id"` // AA node ID
|
||||
Order int16 `json:"order" db:"order"` // Display order
|
||||
TreeID int8 `json:"tree_id" db:"tree_id"` // Tree identifier
|
||||
TemplateID int8 `json:"template_id" db:"template_id"` // Template identifier (1-8)
|
||||
TabID int8 `json:"tab_id" db:"tab_id"` // Tab identifier
|
||||
AAID int32 `json:"aa_id" db:"aa_id"` // AA node ID
|
||||
Order int16 `json:"order" db:"order"` // Display order
|
||||
TreeID int8 `json:"tree_id" db:"tree_id"` // Tree identifier
|
||||
}
|
||||
|
||||
// PlayerAAData represents a player's AA progression
|
||||
type PlayerAAData struct {
|
||||
// Player identification
|
||||
CharacterID int32 `json:"character_id" db:"character_id"`
|
||||
CharacterID int32 `json:"character_id" db:"character_id"`
|
||||
|
||||
// AA progression
|
||||
NodeID int32 `json:"node_id" db:"node_id"` // AA node ID
|
||||
CurrentRank int8 `json:"current_rank" db:"current_rank"` // Current rank in this AA
|
||||
PointsSpent int32 `json:"points_spent" db:"points_spent"` // Total points spent on this AA
|
||||
NodeID int32 `json:"node_id" db:"node_id"` // AA node ID
|
||||
CurrentRank int8 `json:"current_rank" db:"current_rank"` // Current rank in this AA
|
||||
PointsSpent int32 `json:"points_spent" db:"points_spent"` // Total points spent on this AA
|
||||
|
||||
// Template assignment
|
||||
TemplateID int8 `json:"template_id" db:"template_id"` // Template this AA belongs to
|
||||
TabID int8 `json:"tab_id" db:"tab_id"` // Tab this AA belongs to
|
||||
Order int16 `json:"order" db:"order"` // Display order
|
||||
TemplateID int8 `json:"template_id" db:"template_id"` // Template this AA belongs to
|
||||
TabID int8 `json:"tab_id" db:"tab_id"` // Tab this AA belongs to
|
||||
Order int16 `json:"order" db:"order"` // Display order
|
||||
|
||||
// Timestamps
|
||||
PurchasedAt time.Time `json:"purchased_at" db:"purchased_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
PurchasedAt time.Time `json:"purchased_at" db:"purchased_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// AATemplate represents an AA template configuration
|
||||
type AATemplate struct {
|
||||
// Template identification
|
||||
TemplateID int8 `json:"template_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
IsPersonal bool `json:"is_personal"` // True for personal templates (1-3)
|
||||
IsServer bool `json:"is_server"` // True for server templates (4-6)
|
||||
IsCurrent bool `json:"is_current"` // True for current active template
|
||||
TemplateID int8 `json:"template_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
IsPersonal bool `json:"is_personal"` // True for personal templates (1-3)
|
||||
IsServer bool `json:"is_server"` // True for server templates (4-6)
|
||||
IsCurrent bool `json:"is_current"` // True for current active template
|
||||
|
||||
// Template data
|
||||
Entries []*AAEntry `json:"entries"` // AA entries in this template
|
||||
Entries []*AAEntry `json:"entries"` // AA entries in this template
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AATab represents an AA tab with its associated data
|
||||
type AATab struct {
|
||||
// Tab identification
|
||||
TabID int8 `json:"tab_id"`
|
||||
Group int8 `json:"group"`
|
||||
Name string `json:"name"`
|
||||
TabID int8 `json:"tab_id"`
|
||||
Group int8 `json:"group"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Tab configuration
|
||||
MaxAA int32 `json:"max_aa"` // Maximum AA points for this tab
|
||||
ClassID int32 `json:"class_id"` // Associated class ID
|
||||
ExpansionReq int8 `json:"expansion_req"` // Required expansion flags
|
||||
MaxAA int32 `json:"max_aa"` // Maximum AA points for this tab
|
||||
ClassID int32 `json:"class_id"` // Associated class ID
|
||||
ExpansionReq int8 `json:"expansion_req"` // Required expansion flags
|
||||
|
||||
// Current state
|
||||
PointsSpent int32 `json:"points_spent"` // Points spent in this tab
|
||||
PointsSpent int32 `json:"points_spent"` // Points spent in this tab
|
||||
PointsAvailable int32 `json:"points_available"` // Available points for spending
|
||||
|
||||
// AA nodes in this tab
|
||||
Nodes []*AltAdvanceData `json:"nodes"`
|
||||
Nodes []*AltAdvanceData `json:"nodes"`
|
||||
|
||||
// Metadata
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
}
|
||||
|
||||
// AAPlayerState represents a player's complete AA state
|
||||
type AAPlayerState struct {
|
||||
// Player identification
|
||||
CharacterID int32 `json:"character_id"`
|
||||
CharacterID int32 `json:"character_id"`
|
||||
|
||||
// AA points
|
||||
TotalPoints int32 `json:"total_points"` // Total AA points earned
|
||||
SpentPoints int32 `json:"spent_points"` // Total AA points spent
|
||||
TotalPoints int32 `json:"total_points"` // Total AA points earned
|
||||
SpentPoints int32 `json:"spent_points"` // Total AA points spent
|
||||
AvailablePoints int32 `json:"available_points"` // Available AA points
|
||||
BankedPoints int32 `json:"banked_points"` // Banked AA points
|
||||
BankedPoints int32 `json:"banked_points"` // Banked AA points
|
||||
|
||||
// Templates
|
||||
ActiveTemplate int8 `json:"active_template"` // Currently active template
|
||||
Templates map[int8]*AATemplate `json:"templates"` // All templates
|
||||
ActiveTemplate int8 `json:"active_template"` // Currently active template
|
||||
Templates map[int8]*AATemplate `json:"templates"` // All templates
|
||||
|
||||
// Tab states
|
||||
Tabs map[int8]*AATab `json:"tabs"` // Tab states
|
||||
Tabs map[int8]*AATab `json:"tabs"` // Tab states
|
||||
|
||||
// Player AA progression
|
||||
AAProgress map[int32]*PlayerAAData `json:"aa_progress"` // AA node progress by node ID
|
||||
AAProgress map[int32]*PlayerAAData `json:"aa_progress"` // AA node progress by node ID
|
||||
|
||||
// Caching and synchronization
|
||||
mutex sync.RWMutex `json:"-"`
|
||||
lastUpdate time.Time `json:"last_update"`
|
||||
needsSync bool `json:"-"`
|
||||
mutex sync.RWMutex `json:"-"`
|
||||
lastUpdate time.Time `json:"last_update"`
|
||||
needsSync bool `json:"-"`
|
||||
}
|
||||
|
||||
// MasterAAList manages all AA definitions
|
||||
type MasterAAList struct {
|
||||
// AA storage
|
||||
aaList []*AltAdvanceData `json:"aa_list"`
|
||||
aaBySpellID map[int32]*AltAdvanceData `json:"-"` // Fast lookup by spell ID
|
||||
aaByNodeID map[int32]*AltAdvanceData `json:"-"` // Fast lookup by node ID
|
||||
aaByGroup map[int8][]*AltAdvanceData `json:"-"` // Fast lookup by group/tab
|
||||
aaList []*AltAdvanceData `json:"aa_list"`
|
||||
aaBySpellID map[int32]*AltAdvanceData `json:"-"` // Fast lookup by spell ID
|
||||
aaByNodeID map[int32]*AltAdvanceData `json:"-"` // Fast lookup by node ID
|
||||
aaByGroup map[int8][]*AltAdvanceData `json:"-"` // Fast lookup by group/tab
|
||||
|
||||
// Synchronization
|
||||
mutex sync.RWMutex `json:"-"`
|
||||
mutex sync.RWMutex `json:"-"`
|
||||
|
||||
// Statistics
|
||||
totalLoaded int64 `json:"total_loaded"`
|
||||
lastLoadTime time.Time `json:"last_load_time"`
|
||||
totalLoaded int64 `json:"total_loaded"`
|
||||
lastLoadTime time.Time `json:"last_load_time"`
|
||||
}
|
||||
|
||||
// MasterAANodeList manages tree node configurations
|
||||
type MasterAANodeList struct {
|
||||
// Node storage
|
||||
nodeList []*TreeNodeData `json:"node_list"`
|
||||
nodesByClass map[int32][]*TreeNodeData `json:"-"` // Fast lookup by class ID
|
||||
nodesByTree map[int32]*TreeNodeData `json:"-"` // Fast lookup by tree ID
|
||||
nodeList []*TreeNodeData `json:"node_list"`
|
||||
nodesByClass map[int32][]*TreeNodeData `json:"-"` // Fast lookup by class ID
|
||||
nodesByTree map[int32]*TreeNodeData `json:"-"` // Fast lookup by tree ID
|
||||
|
||||
// Synchronization
|
||||
mutex sync.RWMutex `json:"-"`
|
||||
mutex sync.RWMutex `json:"-"`
|
||||
|
||||
// Statistics
|
||||
totalLoaded int64 `json:"total_loaded"`
|
||||
lastLoadTime time.Time `json:"last_load_time"`
|
||||
totalLoaded int64 `json:"total_loaded"`
|
||||
lastLoadTime time.Time `json:"last_load_time"`
|
||||
}
|
||||
|
||||
// AAManager manages the entire AA system
|
||||
type AAManager struct {
|
||||
// Core lists
|
||||
masterAAList *MasterAAList `json:"master_aa_list"`
|
||||
masterNodeList *MasterAANodeList `json:"master_node_list"`
|
||||
masterAAList *MasterAAList `json:"master_aa_list"`
|
||||
masterNodeList *MasterAANodeList `json:"master_node_list"`
|
||||
|
||||
// Player states
|
||||
playerStates map[int32]*AAPlayerState `json:"-"` // Player AA states by character ID
|
||||
statesMutex sync.RWMutex `json:"-"`
|
||||
playerStates map[int32]*AAPlayerState `json:"-"` // Player AA states by character ID
|
||||
statesMutex sync.RWMutex `json:"-"`
|
||||
|
||||
// Configuration
|
||||
config AAManagerConfig `json:"config"`
|
||||
config AAManagerConfig `json:"config"`
|
||||
|
||||
// Database interface
|
||||
database AADatabase `json:"-"`
|
||||
database AADatabase `json:"-"`
|
||||
|
||||
// Packet handler
|
||||
packetHandler AAPacketHandler `json:"-"`
|
||||
packetHandler AAPacketHandler `json:"-"`
|
||||
|
||||
// Event handlers
|
||||
eventHandlers []AAEventHandler `json:"-"`
|
||||
eventMutex sync.RWMutex `json:"-"`
|
||||
eventHandlers []AAEventHandler `json:"-"`
|
||||
eventMutex sync.RWMutex `json:"-"`
|
||||
|
||||
// Statistics
|
||||
stats AAManagerStats `json:"stats"`
|
||||
statsMutex sync.RWMutex `json:"-"`
|
||||
stats AAManagerStats `json:"stats"`
|
||||
statsMutex sync.RWMutex `json:"-"`
|
||||
|
||||
// Background processing
|
||||
stopChan chan struct{} `json:"-"`
|
||||
wg sync.WaitGroup `json:"-"`
|
||||
stopChan chan struct{} `json:"-"`
|
||||
wg sync.WaitGroup `json:"-"`
|
||||
}
|
||||
|
||||
// AAManagerConfig holds configuration for the AA manager
|
||||
type AAManagerConfig struct {
|
||||
// System settings
|
||||
EnableAASystem bool `json:"enable_aa_system"`
|
||||
EnableCaching bool `json:"enable_caching"`
|
||||
EnableValidation bool `json:"enable_validation"`
|
||||
EnableLogging bool `json:"enable_logging"`
|
||||
EnableAASystem bool `json:"enable_aa_system"`
|
||||
EnableCaching bool `json:"enable_caching"`
|
||||
EnableValidation bool `json:"enable_validation"`
|
||||
EnableLogging bool `json:"enable_logging"`
|
||||
|
||||
// Player settings
|
||||
AAPointsPerLevel int32 `json:"aa_points_per_level"`
|
||||
MaxBankedPoints int32 `json:"max_banked_points"`
|
||||
EnableAABanking bool `json:"enable_aa_banking"`
|
||||
AAPointsPerLevel int32 `json:"aa_points_per_level"`
|
||||
MaxBankedPoints int32 `json:"max_banked_points"`
|
||||
EnableAABanking bool `json:"enable_aa_banking"`
|
||||
|
||||
// Performance settings
|
||||
CacheSize int32 `json:"cache_size"`
|
||||
UpdateInterval time.Duration `json:"update_interval"`
|
||||
BatchSize int32 `json:"batch_size"`
|
||||
CacheSize int32 `json:"cache_size"`
|
||||
UpdateInterval time.Duration `json:"update_interval"`
|
||||
BatchSize int32 `json:"batch_size"`
|
||||
|
||||
// Database settings
|
||||
DatabaseEnabled bool `json:"database_enabled"`
|
||||
AutoSave bool `json:"auto_save"`
|
||||
SaveInterval time.Duration `json:"save_interval"`
|
||||
DatabaseEnabled bool `json:"database_enabled"`
|
||||
AutoSave bool `json:"auto_save"`
|
||||
SaveInterval time.Duration `json:"save_interval"`
|
||||
}
|
||||
|
||||
// AAManagerStats holds statistics about the AA system
|
||||
type AAManagerStats struct {
|
||||
// Loading statistics
|
||||
TotalAAsLoaded int64 `json:"total_aas_loaded"`
|
||||
TotalNodesLoaded int64 `json:"total_nodes_loaded"`
|
||||
LastLoadTime time.Time `json:"last_load_time"`
|
||||
LoadDuration time.Duration `json:"load_duration"`
|
||||
TotalAAsLoaded int64 `json:"total_aas_loaded"`
|
||||
TotalNodesLoaded int64 `json:"total_nodes_loaded"`
|
||||
LastLoadTime time.Time `json:"last_load_time"`
|
||||
LoadDuration time.Duration `json:"load_duration"`
|
||||
|
||||
// Player statistics
|
||||
ActivePlayers int64 `json:"active_players"`
|
||||
TotalAAPurchases int64 `json:"total_aa_purchases"`
|
||||
TotalPointsSpent int64 `json:"total_points_spent"`
|
||||
AveragePointsSpent float64 `json:"average_points_spent"`
|
||||
ActivePlayers int64 `json:"active_players"`
|
||||
TotalAAPurchases int64 `json:"total_aa_purchases"`
|
||||
TotalPointsSpent int64 `json:"total_points_spent"`
|
||||
AveragePointsSpent float64 `json:"average_points_spent"`
|
||||
|
||||
// Performance statistics
|
||||
CacheHits int64 `json:"cache_hits"`
|
||||
CacheMisses int64 `json:"cache_misses"`
|
||||
DatabaseQueries int64 `json:"database_queries"`
|
||||
PacketsSent int64 `json:"packets_sent"`
|
||||
CacheHits int64 `json:"cache_hits"`
|
||||
CacheMisses int64 `json:"cache_misses"`
|
||||
DatabaseQueries int64 `json:"database_queries"`
|
||||
PacketsSent int64 `json:"packets_sent"`
|
||||
|
||||
// Tab usage statistics
|
||||
TabUsage map[int8]int64 `json:"tab_usage"`
|
||||
PopularAAs map[int32]int64 `json:"popular_aas"`
|
||||
TabUsage map[int8]int64 `json:"tab_usage"`
|
||||
PopularAAs map[int32]int64 `json:"popular_aas"`
|
||||
|
||||
// Error statistics
|
||||
ValidationErrors int64 `json:"validation_errors"`
|
||||
DatabaseErrors int64 `json:"database_errors"`
|
||||
PacketErrors int64 `json:"packet_errors"`
|
||||
ValidationErrors int64 `json:"validation_errors"`
|
||||
DatabaseErrors int64 `json:"database_errors"`
|
||||
PacketErrors int64 `json:"packet_errors"`
|
||||
|
||||
// Timing statistics
|
||||
LastStatsUpdate time.Time `json:"last_stats_update"`
|
||||
LastStatsUpdate time.Time `json:"last_stats_update"`
|
||||
}
|
||||
|
||||
// DefaultAAManagerConfig returns a default configuration
|
||||
func DefaultAAManagerConfig() AAManagerConfig {
|
||||
return AAManagerConfig{
|
||||
EnableAASystem: DEFAULT_ENABLE_AA_SYSTEM,
|
||||
EnableCaching: DEFAULT_ENABLE_AA_CACHING,
|
||||
EnableValidation: DEFAULT_ENABLE_AA_VALIDATION,
|
||||
EnableLogging: DEFAULT_ENABLE_AA_LOGGING,
|
||||
AAPointsPerLevel: DEFAULT_AA_POINTS_PER_LEVEL,
|
||||
MaxBankedPoints: DEFAULT_AA_MAX_BANKED_POINTS,
|
||||
EnableAABanking: true,
|
||||
CacheSize: AA_CACHE_SIZE,
|
||||
UpdateInterval: time.Duration(AA_UPDATE_INTERVAL) * time.Millisecond,
|
||||
BatchSize: AA_PROCESSING_BATCH_SIZE,
|
||||
DatabaseEnabled: true,
|
||||
AutoSave: true,
|
||||
SaveInterval: 5 * time.Minute,
|
||||
EnableAASystem: DEFAULT_ENABLE_AA_SYSTEM,
|
||||
EnableCaching: DEFAULT_ENABLE_AA_CACHING,
|
||||
EnableValidation: DEFAULT_ENABLE_AA_VALIDATION,
|
||||
EnableLogging: DEFAULT_ENABLE_AA_LOGGING,
|
||||
AAPointsPerLevel: DEFAULT_AA_POINTS_PER_LEVEL,
|
||||
MaxBankedPoints: DEFAULT_AA_MAX_BANKED_POINTS,
|
||||
EnableAABanking: true,
|
||||
CacheSize: AA_CACHE_SIZE,
|
||||
UpdateInterval: time.Duration(AA_UPDATE_INTERVAL) * time.Millisecond,
|
||||
BatchSize: AA_PROCESSING_BATCH_SIZE,
|
||||
DatabaseEnabled: true,
|
||||
AutoSave: true,
|
||||
SaveInterval: 5 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,10 +349,10 @@ func (aad *AltAdvanceData) Copy() *AltAdvanceData {
|
||||
// IsValid validates the AltAdvanceData
|
||||
func (aad *AltAdvanceData) IsValid() bool {
|
||||
return aad.SpellID > 0 &&
|
||||
aad.NodeID > 0 &&
|
||||
len(aad.Name) > 0 &&
|
||||
aad.MaxRank > 0 &&
|
||||
aad.RankCost > 0
|
||||
aad.NodeID > 0 &&
|
||||
len(aad.Name) > 0 &&
|
||||
aad.MaxRank > 0 &&
|
||||
aad.RankCost > 0
|
||||
}
|
||||
|
||||
// GetMaxAAForTab returns the maximum AA points for a given tab
|
||||
|
@ -13,11 +13,11 @@ type Manager struct {
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Statistics
|
||||
totalLookups int64
|
||||
successfulLookups int64
|
||||
failedLookups int64
|
||||
cacheHits int64
|
||||
cacheMisses int64
|
||||
totalLookups int64
|
||||
successfulLookups int64
|
||||
failedLookups int64
|
||||
cacheHits int64
|
||||
cacheMisses int64
|
||||
}
|
||||
|
||||
// NewManager creates a new appearance manager
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -21,14 +21,14 @@ type Channel struct {
|
||||
|
||||
// ChannelMessage represents a message sent to a channel
|
||||
type ChannelMessage struct {
|
||||
SenderID int32
|
||||
SenderName string
|
||||
Message string
|
||||
LanguageID int32
|
||||
ChannelName string
|
||||
Timestamp time.Time
|
||||
IsEmote bool
|
||||
IsOOC bool
|
||||
SenderID int32
|
||||
SenderName string
|
||||
Message string
|
||||
LanguageID int32
|
||||
ChannelName string
|
||||
Timestamp time.Time
|
||||
IsEmote bool
|
||||
IsOOC bool
|
||||
}
|
||||
|
||||
// ChannelMember represents a member in a channel
|
||||
@ -68,19 +68,19 @@ type ChatManager struct {
|
||||
database ChannelDatabase
|
||||
|
||||
// Integration interfaces
|
||||
clientManager ClientManager
|
||||
playerManager PlayerManager
|
||||
clientManager ClientManager
|
||||
playerManager PlayerManager
|
||||
languageProcessor LanguageProcessor
|
||||
}
|
||||
|
||||
// ChatStatistics provides statistics about chat system usage
|
||||
type ChatStatistics struct {
|
||||
TotalChannels int
|
||||
WorldChannels int
|
||||
CustomChannels int
|
||||
TotalMembers int
|
||||
MessagesPerHour int
|
||||
ActiveChannels int
|
||||
TotalChannels int
|
||||
WorldChannels int
|
||||
CustomChannels int
|
||||
TotalMembers int
|
||||
MessagesPerHour int
|
||||
ActiveChannels int
|
||||
}
|
||||
|
||||
// ChannelFilter provides filtering options for channel lists
|
||||
|
@ -10,15 +10,15 @@ const (
|
||||
ClassScout = 31
|
||||
|
||||
// Fighter subclasses
|
||||
ClassWarrior = 2
|
||||
ClassGuardian = 3
|
||||
ClassBerserker = 4
|
||||
ClassBrawler = 5
|
||||
ClassMonk = 6
|
||||
ClassBruiser = 7
|
||||
ClassCrusader = 8
|
||||
ClassWarrior = 2
|
||||
ClassGuardian = 3
|
||||
ClassBerserker = 4
|
||||
ClassBrawler = 5
|
||||
ClassMonk = 6
|
||||
ClassBruiser = 7
|
||||
ClassCrusader = 8
|
||||
ClassShadowknight = 9
|
||||
ClassPaladin = 10
|
||||
ClassPaladin = 10
|
||||
|
||||
// Priest subclasses
|
||||
ClassCleric = 12
|
||||
@ -43,17 +43,17 @@ const (
|
||||
ClassNecromancer = 30
|
||||
|
||||
// Scout subclasses
|
||||
ClassRogue = 32
|
||||
ClassRogue = 32
|
||||
ClassSwashbuckler = 33
|
||||
ClassBrigand = 34
|
||||
ClassBard = 35
|
||||
ClassTroubador = 36
|
||||
ClassDirge = 37
|
||||
ClassPredator = 38
|
||||
ClassRanger = 39
|
||||
ClassAssassin = 40
|
||||
ClassAnimalist = 41
|
||||
ClassBeastlord = 42
|
||||
ClassBrigand = 34
|
||||
ClassBard = 35
|
||||
ClassTroubador = 36
|
||||
ClassDirge = 37
|
||||
ClassPredator = 38
|
||||
ClassRanger = 39
|
||||
ClassAssassin = 40
|
||||
ClassAnimalist = 41
|
||||
ClassBeastlord = 42
|
||||
|
||||
// Special classes
|
||||
ClassShaper = 43
|
||||
|
@ -1,7 +1,6 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
func NewCollection() *Collection {
|
||||
return &Collection{
|
||||
collectionItems: make([]CollectionItem, 0),
|
||||
rewardItems: make([]CollectionRewardItem, 0),
|
||||
rewardItems: make([]CollectionRewardItem, 0),
|
||||
selectableRewardItems: make([]CollectionRewardItem, 0),
|
||||
lastModified: time.Now(),
|
||||
lastModified: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,17 +28,17 @@ func NewCollectionFromData(source *Collection) *Collection {
|
||||
|
||||
collection := &Collection{
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
category: source.category,
|
||||
level: source.level,
|
||||
rewardCoin: source.rewardCoin,
|
||||
rewardXP: source.rewardXP,
|
||||
completed: source.completed,
|
||||
saveNeeded: source.saveNeeded,
|
||||
collectionItems: make([]CollectionItem, len(source.collectionItems)),
|
||||
rewardItems: make([]CollectionRewardItem, len(source.rewardItems)),
|
||||
name: source.name,
|
||||
category: source.category,
|
||||
level: source.level,
|
||||
rewardCoin: source.rewardCoin,
|
||||
rewardXP: source.rewardXP,
|
||||
completed: source.completed,
|
||||
saveNeeded: source.saveNeeded,
|
||||
collectionItems: make([]CollectionItem, len(source.collectionItems)),
|
||||
rewardItems: make([]CollectionRewardItem, len(source.rewardItems)),
|
||||
selectableRewardItems: make([]CollectionRewardItem, len(source.selectableRewardItems)),
|
||||
lastModified: time.Now(),
|
||||
lastModified: time.Now(),
|
||||
}
|
||||
|
||||
// Deep copy collection items
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewMasterCollectionList creates a new master collection list
|
||||
|
@ -20,19 +20,19 @@ type CollectionRewardItem struct {
|
||||
|
||||
// Collection represents a collection that players can complete
|
||||
type Collection struct {
|
||||
mu sync.RWMutex
|
||||
id int32
|
||||
name string
|
||||
category string
|
||||
level int8
|
||||
rewardCoin int64
|
||||
rewardXP int64
|
||||
completed bool
|
||||
saveNeeded bool
|
||||
collectionItems []CollectionItem
|
||||
rewardItems []CollectionRewardItem
|
||||
selectableRewardItems []CollectionRewardItem
|
||||
lastModified time.Time
|
||||
mu sync.RWMutex
|
||||
id int32
|
||||
name string
|
||||
category string
|
||||
level int8
|
||||
rewardCoin int64
|
||||
rewardXP int64
|
||||
completed bool
|
||||
saveNeeded bool
|
||||
collectionItems []CollectionItem
|
||||
rewardItems []CollectionRewardItem
|
||||
selectableRewardItems []CollectionRewardItem
|
||||
lastModified time.Time
|
||||
}
|
||||
|
||||
// CollectionData represents collection data for database operations
|
||||
@ -60,9 +60,9 @@ type PlayerCollectionData struct {
|
||||
|
||||
// PlayerCollectionItemData represents player found collection items
|
||||
type PlayerCollectionItemData struct {
|
||||
CharacterID int32 `json:"char_id" db:"char_id"`
|
||||
CollectionID int32 `json:"collection_id" db:"collection_id"`
|
||||
CollectionItemID int32 `json:"collection_item_id" db:"collection_item_id"`
|
||||
CharacterID int32 `json:"char_id" db:"char_id"`
|
||||
CollectionID int32 `json:"collection_id" db:"collection_id"`
|
||||
CollectionItemID int32 `json:"collection_item_id" db:"collection_item_id"`
|
||||
}
|
||||
|
||||
// MasterCollectionList manages all available collections in the game
|
||||
@ -100,31 +100,31 @@ type CollectionStatistics struct {
|
||||
|
||||
// CollectionInfo provides basic collection information
|
||||
type CollectionInfo struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
Level int8 `json:"level"`
|
||||
Completed bool `json:"completed"`
|
||||
ReadyToTurnIn bool `json:"ready_to_turn_in"`
|
||||
ItemsFound int `json:"items_found"`
|
||||
ItemsTotal int `json:"items_total"`
|
||||
RewardCoin int64 `json:"reward_coin"`
|
||||
RewardXP int64 `json:"reward_xp"`
|
||||
RewardItems []CollectionRewardItem `json:"reward_items"`
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
Level int8 `json:"level"`
|
||||
Completed bool `json:"completed"`
|
||||
ReadyToTurnIn bool `json:"ready_to_turn_in"`
|
||||
ItemsFound int `json:"items_found"`
|
||||
ItemsTotal int `json:"items_total"`
|
||||
RewardCoin int64 `json:"reward_coin"`
|
||||
RewardXP int64 `json:"reward_xp"`
|
||||
RewardItems []CollectionRewardItem `json:"reward_items"`
|
||||
SelectableRewards []CollectionRewardItem `json:"selectable_rewards"`
|
||||
RequiredItems []CollectionItem `json:"required_items"`
|
||||
RequiredItems []CollectionItem `json:"required_items"`
|
||||
}
|
||||
|
||||
// CollectionProgress represents player progress on a collection
|
||||
type CollectionProgress struct {
|
||||
CollectionID int32 `json:"collection_id"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
Level int8 `json:"level"`
|
||||
Completed bool `json:"completed"`
|
||||
ReadyToTurnIn bool `json:"ready_to_turn_in"`
|
||||
Progress float64 `json:"progress_percentage"`
|
||||
ItemsFound []CollectionItem `json:"items_found"`
|
||||
ItemsNeeded []CollectionItem `json:"items_needed"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
CollectionID int32 `json:"collection_id"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
Level int8 `json:"level"`
|
||||
Completed bool `json:"completed"`
|
||||
ReadyToTurnIn bool `json:"ready_to_turn_in"`
|
||||
Progress float64 `json:"progress_percentage"`
|
||||
ItemsFound []CollectionItem `json:"items_found"`
|
||||
ItemsNeeded []CollectionItem `json:"items_needed"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
@ -32,10 +32,10 @@ func (vs *VisualState) GetName() string {
|
||||
|
||||
// Emote represents an emote with visual state and messages
|
||||
type Emote struct {
|
||||
name string
|
||||
visualState int32
|
||||
message string
|
||||
targetedMessage string
|
||||
name string
|
||||
visualState int32
|
||||
message string
|
||||
targetedMessage string
|
||||
}
|
||||
|
||||
// NewEmote creates a new emote
|
||||
|
@ -25,13 +25,13 @@ var NewSpellEffectManager = spells.NewSpellEffectManager
|
||||
|
||||
// Re-export constants
|
||||
const (
|
||||
ControlEffectStun = spells.ControlEffectStun
|
||||
ControlEffectRoot = spells.ControlEffectRoot
|
||||
ControlEffectMez = spells.ControlEffectMez
|
||||
ControlEffectDaze = spells.ControlEffectDaze
|
||||
ControlEffectFear = spells.ControlEffectFear
|
||||
ControlEffectSlow = spells.ControlEffectSlow
|
||||
ControlEffectStun = spells.ControlEffectStun
|
||||
ControlEffectRoot = spells.ControlEffectRoot
|
||||
ControlEffectMez = spells.ControlEffectMez
|
||||
ControlEffectDaze = spells.ControlEffectDaze
|
||||
ControlEffectFear = spells.ControlEffectFear
|
||||
ControlEffectSlow = spells.ControlEffectSlow
|
||||
ControlEffectSnare = spells.ControlEffectSnare
|
||||
ControlEffectCharm = spells.ControlEffectCharm
|
||||
ControlMaxEffects = spells.ControlMaxEffects
|
||||
ControlMaxEffects = spells.ControlMaxEffects
|
||||
)
|
@ -7,11 +7,11 @@ const (
|
||||
MinFactionValue = -50000
|
||||
|
||||
// Special faction ID ranges
|
||||
SpecialFactionIDMax = 10 // Faction IDs <= 10 are special (not real factions)
|
||||
SpecialFactionIDMax = 10 // Faction IDs <= 10 are special (not real factions)
|
||||
|
||||
// Faction consideration (con) ranges
|
||||
MinCon = -4 // Hostile
|
||||
MaxCon = 4 // Ally
|
||||
MinCon = -4 // Hostile
|
||||
MaxCon = 4 // Ally
|
||||
|
||||
// Con value thresholds
|
||||
ConNeutralMin = -9999
|
||||
@ -34,13 +34,13 @@ const AttackThreshold = -4
|
||||
|
||||
// Default faction consideration values
|
||||
const (
|
||||
ConKOS = -4 // Kill on sight
|
||||
ConThreat = -3 // Threatening
|
||||
ConDubious = -2 // Dubiously
|
||||
ConAppre = -1 // Apprehensive
|
||||
ConIndiff = 0 // Indifferent
|
||||
ConAmiable = 1 // Amiable
|
||||
ConKindly = 2 // Kindly
|
||||
ConWarmly = 3 // Warmly
|
||||
ConAlly = 4 // Ally
|
||||
ConKOS = -4 // Kill on sight
|
||||
ConThreat = -3 // Threatening
|
||||
ConDubious = -2 // Dubiously
|
||||
ConAppre = -1 // Apprehensive
|
||||
ConIndiff = 0 // Indifferent
|
||||
ConAmiable = 1 // Amiable
|
||||
ConKindly = 2 // Kindly
|
||||
ConWarmly = 3 // Warmly
|
||||
ConAlly = 4 // Ally
|
||||
)
|
@ -13,12 +13,12 @@ type Manager struct {
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Statistics
|
||||
totalFactionChanges int64
|
||||
factionIncreases int64
|
||||
factionDecreases int64
|
||||
factionLookups int64
|
||||
playersWithFactions int64
|
||||
changesByFaction map[int32]int64 // Faction ID -> total changes
|
||||
totalFactionChanges int64
|
||||
factionIncreases int64
|
||||
factionDecreases int64
|
||||
factionLookups int64
|
||||
playersWithFactions int64
|
||||
changesByFaction map[int32]int64 // Faction ID -> total changes
|
||||
}
|
||||
|
||||
// NewManager creates a new faction manager
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
|
||||
// MasterFactionList manages all factions in the game
|
||||
type MasterFactionList struct {
|
||||
globalFactionList map[int32]*Faction // Factions by ID
|
||||
factionNameList map[string]*Faction // Factions by name
|
||||
hostileFactions map[int32][]int32 // Hostile faction relationships
|
||||
friendlyFactions map[int32][]int32 // Friendly faction relationships
|
||||
mutex sync.RWMutex // Thread safety
|
||||
globalFactionList map[int32]*Faction // Factions by ID
|
||||
factionNameList map[string]*Faction // Factions by name
|
||||
hostileFactions map[int32][]int32 // Hostile faction relationships
|
||||
friendlyFactions map[int32][]int32 // Friendly faction relationships
|
||||
mutex sync.RWMutex // Thread safety
|
||||
}
|
||||
|
||||
// NewMasterFactionList creates a new master faction list
|
||||
|
@ -6,12 +6,12 @@ import (
|
||||
|
||||
// PlayerFaction manages faction standing for a single player
|
||||
type PlayerFaction struct {
|
||||
factionValues map[int32]int32 // Faction ID -> current value
|
||||
factionPercent map[int32]int8 // Faction ID -> percentage within con level
|
||||
factionUpdateNeeded []int32 // Factions that need client updates
|
||||
factionValues map[int32]int32 // Faction ID -> current value
|
||||
factionPercent map[int32]int8 // Faction ID -> percentage within con level
|
||||
factionUpdateNeeded []int32 // Factions that need client updates
|
||||
masterFactionList *MasterFactionList
|
||||
updateMutex sync.Mutex // Thread safety for updates
|
||||
mutex sync.RWMutex // Thread safety for faction data
|
||||
updateMutex sync.Mutex // Thread safety for updates
|
||||
mutex sync.RWMutex // Thread safety for faction data
|
||||
}
|
||||
|
||||
// NewPlayerFaction creates a new player faction system
|
||||
|
@ -2,25 +2,25 @@ package factions
|
||||
|
||||
// Faction represents a single faction with its properties
|
||||
type Faction struct {
|
||||
ID int32 // Faction ID
|
||||
Name string // Faction name
|
||||
Type string // Faction type/category
|
||||
Description string // Faction description
|
||||
NegativeChange int16 // Amount faction decreases by default
|
||||
PositiveChange int16 // Amount faction increases by default
|
||||
DefaultValue int32 // Default faction value for new characters
|
||||
ID int32 // Faction ID
|
||||
Name string // Faction name
|
||||
Type string // Faction type/category
|
||||
Description string // Faction description
|
||||
NegativeChange int16 // Amount faction decreases by default
|
||||
PositiveChange int16 // Amount faction increases by default
|
||||
DefaultValue int32 // Default faction value for new characters
|
||||
}
|
||||
|
||||
// NewFaction creates a new faction with the given parameters
|
||||
func NewFaction(id int32, name, factionType, description string) *Faction {
|
||||
return &Faction{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Type: factionType,
|
||||
Description: description,
|
||||
NegativeChange: 0,
|
||||
PositiveChange: 0,
|
||||
DefaultValue: 0,
|
||||
ID: id,
|
||||
Name: name,
|
||||
Type: factionType,
|
||||
Description: description,
|
||||
NegativeChange: 0,
|
||||
PositiveChange: 0,
|
||||
DefaultValue: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,13 +77,13 @@ func (f *Faction) SetDefaultValue(value int32) {
|
||||
// Clone creates a copy of the faction
|
||||
func (f *Faction) Clone() *Faction {
|
||||
return &Faction{
|
||||
ID: f.ID,
|
||||
Name: f.Name,
|
||||
Type: f.Type,
|
||||
Description: f.Description,
|
||||
NegativeChange: f.NegativeChange,
|
||||
PositiveChange: f.PositiveChange,
|
||||
DefaultValue: f.DefaultValue,
|
||||
ID: f.ID,
|
||||
Name: f.Name,
|
||||
Type: f.Type,
|
||||
Description: f.Description,
|
||||
NegativeChange: f.NegativeChange,
|
||||
PositiveChange: f.PositiveChange,
|
||||
DefaultValue: f.DefaultValue,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,18 +55,18 @@ const (
|
||||
|
||||
// Default spawn configuration
|
||||
const (
|
||||
DefaultDifficulty = 0
|
||||
DefaultSpawnType = 2
|
||||
DefaultState = 129
|
||||
DefaultDifficulty = 0
|
||||
DefaultSpawnType = 2
|
||||
DefaultState = 129
|
||||
DefaultAttemptsPerHarvest = 1
|
||||
DefaultNumberHarvests = 1
|
||||
DefaultRandomizeHeading = true
|
||||
DefaultNumberHarvests = 1
|
||||
DefaultRandomizeHeading = true
|
||||
)
|
||||
|
||||
// Harvest message channels (placeholder values)
|
||||
const (
|
||||
ChannelHarvesting = 15
|
||||
ChannelColorRed = 13
|
||||
ChannelHarvesting = 15
|
||||
ChannelColorRed = 13
|
||||
)
|
||||
|
||||
// Statistical tracking
|
||||
|
@ -142,9 +142,9 @@ type GroundSpawnAware interface {
|
||||
|
||||
// PlayerGroundSpawnAdapter provides ground spawn functionality for players
|
||||
type PlayerGroundSpawnAdapter struct {
|
||||
player *Player
|
||||
manager *Manager
|
||||
logger Logger
|
||||
player *Player
|
||||
manager *Manager
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// NewPlayerGroundSpawnAdapter creates a new player ground spawn adapter
|
||||
|
@ -2,21 +2,20 @@ package ground_spawn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewManager creates a new ground spawn manager
|
||||
func NewManager(database Database, logger Logger) *Manager {
|
||||
return &Manager{
|
||||
groundSpawns: make(map[int32]*GroundSpawn),
|
||||
spawnsByZone: make(map[int32][]*GroundSpawn),
|
||||
entriesByID: make(map[int32][]*GroundSpawnEntry),
|
||||
itemsByID: make(map[int32][]*GroundSpawnEntryItem),
|
||||
respawnQueue: make(map[int32]time.Time),
|
||||
database: database,
|
||||
logger: logger,
|
||||
harvestsBySkill: make(map[string]int64),
|
||||
groundSpawns: make(map[int32]*GroundSpawn),
|
||||
spawnsByZone: make(map[int32][]*GroundSpawn),
|
||||
entriesByID: make(map[int32][]*GroundSpawnEntry),
|
||||
itemsByID: make(map[int32][]*GroundSpawnEntryItem),
|
||||
respawnQueue: make(map[int32]time.Time),
|
||||
database: database,
|
||||
logger: logger,
|
||||
harvestsBySkill: make(map[string]int64),
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,14 +228,14 @@ func (m *Manager) buildHarvestContext(gs *GroundSpawn, player *Player) (*Harvest
|
||||
}
|
||||
|
||||
return &HarvestContext{
|
||||
Player: player,
|
||||
GroundSpawn: gs,
|
||||
PlayerSkill: playerSkill,
|
||||
TotalSkill: totalSkill,
|
||||
GroundSpawnEntries: entries,
|
||||
GroundSpawnItems: items,
|
||||
IsCollection: gs.GetCollectionSkill() == SkillCollecting,
|
||||
MaxSkillRequired: maxSkillRequired,
|
||||
Player: player,
|
||||
GroundSpawn: gs,
|
||||
PlayerSkill: playerSkill,
|
||||
TotalSkill: totalSkill,
|
||||
GroundSpawnEntries: entries,
|
||||
GroundSpawnItems: items,
|
||||
IsCollection: gs.GetCollectionSkill() == SkillCollecting,
|
||||
MaxSkillRequired: maxSkillRequired,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -302,13 +301,13 @@ func (m *Manager) GetStatistics() *HarvestStatistics {
|
||||
}
|
||||
|
||||
return &HarvestStatistics{
|
||||
TotalHarvests: m.totalHarvests,
|
||||
SuccessfulHarvests: m.successfulHarvests,
|
||||
RareItemsHarvested: m.rareItemsHarvested,
|
||||
SkillUpsGenerated: m.skillUpsGenerated,
|
||||
HarvestsBySkill: harvestsBySkill,
|
||||
ActiveGroundSpawns: len(m.groundSpawns),
|
||||
GroundSpawnsByZone: spawnsByZone,
|
||||
TotalHarvests: m.totalHarvests,
|
||||
SuccessfulHarvests: m.successfulHarvests,
|
||||
RareItemsHarvested: m.rareItemsHarvested,
|
||||
SkillUpsGenerated: m.skillUpsGenerated,
|
||||
HarvestsBySkill: harvestsBySkill,
|
||||
ActiveGroundSpawns: len(m.groundSpawns),
|
||||
GroundSpawnsByZone: spawnsByZone,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,22 +4,21 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/common"
|
||||
"eq2emu/internal/spawn"
|
||||
)
|
||||
|
||||
// GroundSpawn represents a harvestable resource node in the game world
|
||||
type GroundSpawn struct {
|
||||
*spawn.Spawn // Embed spawn for base functionality
|
||||
*spawn.Spawn // Embed spawn for base functionality
|
||||
|
||||
numberHarvests int8 // Number of harvests remaining
|
||||
numAttemptsPerHarvest int8 // Attempts per harvest session
|
||||
groundspawnID int32 // Database ID for this groundspawn entry
|
||||
collectionSkill string // Required skill for harvesting
|
||||
randomizeHeading bool // Whether to randomize heading on spawn
|
||||
numberHarvests int8 // Number of harvests remaining
|
||||
numAttemptsPerHarvest int8 // Attempts per harvest session
|
||||
groundspawnID int32 // Database ID for this groundspawn entry
|
||||
collectionSkill string // Required skill for harvesting
|
||||
randomizeHeading bool // Whether to randomize heading on spawn
|
||||
|
||||
harvestMutex sync.Mutex // Thread safety for harvest operations
|
||||
harvestUseMutex sync.Mutex // Thread safety for use operations
|
||||
harvestMutex sync.Mutex // Thread safety for harvest operations
|
||||
harvestUseMutex sync.Mutex // Thread safety for use operations
|
||||
}
|
||||
|
||||
// GroundSpawnEntry represents harvest table data from database
|
||||
@ -38,40 +37,40 @@ type GroundSpawnEntry struct {
|
||||
|
||||
// GroundSpawnEntryItem represents items that can be harvested
|
||||
type GroundSpawnEntryItem struct {
|
||||
ItemID int32 // Item database ID
|
||||
IsRare int8 // 0=normal, 1=rare, 2=imbue
|
||||
GridID int32 // Grid restriction (0=any)
|
||||
Quantity int16 // Item quantity (usually 1)
|
||||
ItemID int32 // Item database ID
|
||||
IsRare int8 // 0=normal, 1=rare, 2=imbue
|
||||
GridID int32 // Grid restriction (0=any)
|
||||
Quantity int16 // Item quantity (usually 1)
|
||||
}
|
||||
|
||||
// HarvestResult represents the outcome of a harvest attempt
|
||||
type HarvestResult struct {
|
||||
Success bool // Whether harvest succeeded
|
||||
HarvestType int8 // Type of harvest achieved
|
||||
ItemsAwarded []*HarvestedItem // Items given to player
|
||||
MessageText string // Message to display to player
|
||||
SkillGained bool // Whether skill was gained
|
||||
Error error // Any error that occurred
|
||||
Success bool // Whether harvest succeeded
|
||||
HarvestType int8 // Type of harvest achieved
|
||||
ItemsAwarded []*HarvestedItem // Items given to player
|
||||
MessageText string // Message to display to player
|
||||
SkillGained bool // Whether skill was gained
|
||||
Error error // Any error that occurred
|
||||
}
|
||||
|
||||
// HarvestedItem represents an item awarded from harvesting
|
||||
type HarvestedItem struct {
|
||||
ItemID int32 // Database item ID
|
||||
Quantity int16 // Number of items
|
||||
IsRare bool // Whether this is a rare item
|
||||
Name string // Item name for messages
|
||||
ItemID int32 // Database item ID
|
||||
Quantity int16 // Number of items
|
||||
IsRare bool // Whether this is a rare item
|
||||
Name string // Item name for messages
|
||||
}
|
||||
|
||||
// HarvestContext contains all data needed for a harvest operation
|
||||
type HarvestContext struct {
|
||||
Player *Player // Player attempting harvest
|
||||
GroundSpawn *GroundSpawn // The ground spawn being harvested
|
||||
PlayerSkill *Skill // Player's harvesting skill
|
||||
TotalSkill int16 // Total skill including bonuses
|
||||
GroundSpawnEntries []*GroundSpawnEntry // Available harvest tables
|
||||
GroundSpawnItems []*GroundSpawnEntryItem // Available harvest items
|
||||
IsCollection bool // Whether this is collection harvesting
|
||||
MaxSkillRequired int16 // Maximum skill required for any table
|
||||
Player *Player // Player attempting harvest
|
||||
GroundSpawn *GroundSpawn // The ground spawn being harvested
|
||||
PlayerSkill *Skill // Player's harvesting skill
|
||||
TotalSkill int16 // Total skill including bonuses
|
||||
GroundSpawnEntries []*GroundSpawnEntry // Available harvest tables
|
||||
GroundSpawnItems []*GroundSpawnEntryItem // Available harvest items
|
||||
IsCollection bool // Whether this is collection harvesting
|
||||
MaxSkillRequired int16 // Maximum skill required for any table
|
||||
}
|
||||
|
||||
// SpawnLocation represents a spawn position with grid information
|
||||
@ -93,45 +92,45 @@ type HarvestModifiers struct {
|
||||
|
||||
// GroundSpawnConfig contains configuration for ground spawn creation
|
||||
type GroundSpawnConfig struct {
|
||||
GroundSpawnID int32 // Database entry ID
|
||||
CollectionSkill string // Required harvesting skill
|
||||
NumberHarvests int8 // Harvests before depletion
|
||||
AttemptsPerHarvest int8 // Attempts per harvest session
|
||||
RandomizeHeading bool // Randomize spawn heading
|
||||
RespawnTimer time.Duration // Time before respawn
|
||||
Location SpawnLocation // Spawn position
|
||||
Name string // Display name
|
||||
Description string // Spawn description
|
||||
GroundSpawnID int32 // Database entry ID
|
||||
CollectionSkill string // Required harvesting skill
|
||||
NumberHarvests int8 // Harvests before depletion
|
||||
AttemptsPerHarvest int8 // Attempts per harvest session
|
||||
RandomizeHeading bool // Randomize spawn heading
|
||||
RespawnTimer time.Duration // Time before respawn
|
||||
Location SpawnLocation // Spawn position
|
||||
Name string // Display name
|
||||
Description string // Spawn description
|
||||
}
|
||||
|
||||
// Manager manages all ground spawn operations
|
||||
type Manager struct {
|
||||
groundSpawns map[int32]*GroundSpawn // Active ground spawns by ID
|
||||
spawnsByZone map[int32][]*GroundSpawn // Ground spawns by zone ID
|
||||
entriesByID map[int32][]*GroundSpawnEntry // Harvest entries by groundspawn ID
|
||||
itemsByID map[int32][]*GroundSpawnEntryItem // Harvest items by groundspawn ID
|
||||
respawnQueue map[int32]time.Time // Respawn timestamps
|
||||
groundSpawns map[int32]*GroundSpawn // Active ground spawns by ID
|
||||
spawnsByZone map[int32][]*GroundSpawn // Ground spawns by zone ID
|
||||
entriesByID map[int32][]*GroundSpawnEntry // Harvest entries by groundspawn ID
|
||||
itemsByID map[int32][]*GroundSpawnEntryItem // Harvest items by groundspawn ID
|
||||
respawnQueue map[int32]time.Time // Respawn timestamps
|
||||
|
||||
database Database // Database interface
|
||||
logger Logger // Logging interface
|
||||
database Database // Database interface
|
||||
logger Logger // Logging interface
|
||||
|
||||
mutex sync.RWMutex // Thread safety
|
||||
mutex sync.RWMutex // Thread safety
|
||||
|
||||
// Statistics
|
||||
totalHarvests int64 // Total harvest attempts
|
||||
successfulHarvests int64 // Successful harvests
|
||||
rareItemsHarvested int64 // Rare items harvested
|
||||
skillUpsGenerated int64 // Skill increases given
|
||||
harvestsBySkill map[string]int64 // Harvests by skill type
|
||||
totalHarvests int64 // Total harvest attempts
|
||||
successfulHarvests int64 // Successful harvests
|
||||
rareItemsHarvested int64 // Rare items harvested
|
||||
skillUpsGenerated int64 // Skill increases given
|
||||
harvestsBySkill map[string]int64 // Harvests by skill type
|
||||
}
|
||||
|
||||
// HarvestStatistics contains harvest system statistics
|
||||
type HarvestStatistics struct {
|
||||
TotalHarvests int64 `json:"total_harvests"`
|
||||
SuccessfulHarvests int64 `json:"successful_harvests"`
|
||||
RareItemsHarvested int64 `json:"rare_items_harvested"`
|
||||
SkillUpsGenerated int64 `json:"skill_ups_generated"`
|
||||
HarvestsBySkill map[string]int64 `json:"harvests_by_skill"`
|
||||
ActiveGroundSpawns int `json:"active_ground_spawns"`
|
||||
GroundSpawnsByZone map[int32]int `json:"ground_spawns_by_zone"`
|
||||
TotalHarvests int64 `json:"total_harvests"`
|
||||
SuccessfulHarvests int64 `json:"successful_harvests"`
|
||||
RareItemsHarvested int64 `json:"rare_items_harvested"`
|
||||
SkillUpsGenerated int64 `json:"skill_ups_generated"`
|
||||
HarvestsBySkill map[string]int64 `json:"harvests_by_skill"`
|
||||
ActiveGroundSpawns int `json:"active_ground_spawns"`
|
||||
GroundSpawnsByZone map[int32]int `json:"ground_spawns_by_zone"`
|
||||
}
|
@ -2,106 +2,106 @@ package groups
|
||||
|
||||
// Group loot method constants
|
||||
const (
|
||||
LOOT_METHOD_LEADER_ONLY = 0
|
||||
LOOT_METHOD_ROUND_ROBIN = 1
|
||||
LOOT_METHOD_LEADER_ONLY = 0
|
||||
LOOT_METHOD_ROUND_ROBIN = 1
|
||||
LOOT_METHOD_NEED_BEFORE_GREED = 2
|
||||
LOOT_METHOD_LOTTO = 3
|
||||
LOOT_METHOD_LOTTO = 3
|
||||
)
|
||||
|
||||
// Group loot rarity constants
|
||||
const (
|
||||
LOOT_RARITY_COMMON = 0
|
||||
LOOT_RARITY_UNCOMMON = 1
|
||||
LOOT_RARITY_RARE = 2
|
||||
LOOT_RARITY_LEGENDARY = 3
|
||||
LOOT_RARITY_FABLED = 4
|
||||
LOOT_RARITY_COMMON = 0
|
||||
LOOT_RARITY_UNCOMMON = 1
|
||||
LOOT_RARITY_RARE = 2
|
||||
LOOT_RARITY_LEGENDARY = 3
|
||||
LOOT_RARITY_FABLED = 4
|
||||
)
|
||||
|
||||
// Group auto-split constants
|
||||
const (
|
||||
AUTO_SPLIT_DISABLED = 0
|
||||
AUTO_SPLIT_ENABLED = 1
|
||||
AUTO_SPLIT_DISABLED = 0
|
||||
AUTO_SPLIT_ENABLED = 1
|
||||
)
|
||||
|
||||
// Group lock method constants
|
||||
const (
|
||||
LOCK_METHOD_OPEN = 0
|
||||
LOCK_METHOD_INVITE_ONLY = 1
|
||||
LOCK_METHOD_CLOSED = 2
|
||||
LOCK_METHOD_OPEN = 0
|
||||
LOCK_METHOD_INVITE_ONLY = 1
|
||||
LOCK_METHOD_CLOSED = 2
|
||||
)
|
||||
|
||||
// Group auto-lock constants
|
||||
const (
|
||||
AUTO_LOCK_DISABLED = 0
|
||||
AUTO_LOCK_ENABLED = 1
|
||||
AUTO_LOCK_DISABLED = 0
|
||||
AUTO_LOCK_ENABLED = 1
|
||||
)
|
||||
|
||||
// Group auto-loot method constants
|
||||
const (
|
||||
AUTO_LOOT_DISABLED = 0
|
||||
AUTO_LOOT_ENABLED = 1
|
||||
AUTO_LOOT_DISABLED = 0
|
||||
AUTO_LOOT_ENABLED = 1
|
||||
)
|
||||
|
||||
// Default yell constants
|
||||
const (
|
||||
DEFAULT_YELL_DISABLED = 0
|
||||
DEFAULT_YELL_ENABLED = 1
|
||||
DEFAULT_YELL_DISABLED = 0
|
||||
DEFAULT_YELL_ENABLED = 1
|
||||
)
|
||||
|
||||
// Group size limits
|
||||
const (
|
||||
MAX_GROUP_SIZE = 6
|
||||
MAX_RAID_GROUPS = 4
|
||||
MAX_RAID_SIZE = MAX_GROUP_SIZE * MAX_RAID_GROUPS
|
||||
MAX_GROUP_SIZE = 6
|
||||
MAX_RAID_GROUPS = 4
|
||||
MAX_RAID_SIZE = MAX_GROUP_SIZE * MAX_RAID_GROUPS
|
||||
)
|
||||
|
||||
// Group member position constants
|
||||
const (
|
||||
GROUP_POSITION_LEADER = 0
|
||||
GROUP_POSITION_MEMBER_1 = 1
|
||||
GROUP_POSITION_MEMBER_2 = 2
|
||||
GROUP_POSITION_MEMBER_3 = 3
|
||||
GROUP_POSITION_MEMBER_4 = 4
|
||||
GROUP_POSITION_MEMBER_5 = 5
|
||||
GROUP_POSITION_LEADER = 0
|
||||
GROUP_POSITION_MEMBER_1 = 1
|
||||
GROUP_POSITION_MEMBER_2 = 2
|
||||
GROUP_POSITION_MEMBER_3 = 3
|
||||
GROUP_POSITION_MEMBER_4 = 4
|
||||
GROUP_POSITION_MEMBER_5 = 5
|
||||
)
|
||||
|
||||
// Group invite error codes
|
||||
const (
|
||||
GROUP_INVITE_SUCCESS = 0
|
||||
GROUP_INVITE_ALREADY_IN_GROUP = 1
|
||||
GROUP_INVITE_ALREADY_HAS_INVITE = 2
|
||||
GROUP_INVITE_GROUP_FULL = 3
|
||||
GROUP_INVITE_DECLINED = 4
|
||||
GROUP_INVITE_TARGET_NOT_FOUND = 5
|
||||
GROUP_INVITE_SELF_INVITE = 6
|
||||
GROUP_INVITE_PERMISSION_DENIED = 7
|
||||
GROUP_INVITE_TARGET_BUSY = 8
|
||||
GROUP_INVITE_SUCCESS = 0
|
||||
GROUP_INVITE_ALREADY_IN_GROUP = 1
|
||||
GROUP_INVITE_ALREADY_HAS_INVITE = 2
|
||||
GROUP_INVITE_GROUP_FULL = 3
|
||||
GROUP_INVITE_DECLINED = 4
|
||||
GROUP_INVITE_TARGET_NOT_FOUND = 5
|
||||
GROUP_INVITE_SELF_INVITE = 6
|
||||
GROUP_INVITE_PERMISSION_DENIED = 7
|
||||
GROUP_INVITE_TARGET_BUSY = 8
|
||||
)
|
||||
|
||||
// Group message types
|
||||
const (
|
||||
GROUP_MESSAGE_TYPE_SYSTEM = 0
|
||||
GROUP_MESSAGE_TYPE_COMBAT = 1
|
||||
GROUP_MESSAGE_TYPE_LOOT = 2
|
||||
GROUP_MESSAGE_TYPE_QUEST = 3
|
||||
GROUP_MESSAGE_TYPE_CHAT = 4
|
||||
GROUP_MESSAGE_TYPE_SYSTEM = 0
|
||||
GROUP_MESSAGE_TYPE_COMBAT = 1
|
||||
GROUP_MESSAGE_TYPE_LOOT = 2
|
||||
GROUP_MESSAGE_TYPE_QUEST = 3
|
||||
GROUP_MESSAGE_TYPE_CHAT = 4
|
||||
)
|
||||
|
||||
// Channel constants for group communication
|
||||
const (
|
||||
CHANNEL_GROUP_SAY = 11
|
||||
CHANNEL_GROUP_CHAT = 31
|
||||
CHANNEL_RAID_SAY = 35
|
||||
CHANNEL_GROUP_SAY = 11
|
||||
CHANNEL_GROUP_CHAT = 31
|
||||
CHANNEL_RAID_SAY = 35
|
||||
)
|
||||
|
||||
// Group update flags
|
||||
const (
|
||||
GROUP_UPDATE_FLAG_MEMBER_LIST = 1 << 0
|
||||
GROUP_UPDATE_FLAG_MEMBER_STATS = 1 << 1
|
||||
GROUP_UPDATE_FLAG_MEMBER_ZONE = 1 << 2
|
||||
GROUP_UPDATE_FLAG_LEADERSHIP = 1 << 3
|
||||
GROUP_UPDATE_FLAG_OPTIONS = 1 << 4
|
||||
GROUP_UPDATE_FLAG_RAID_INFO = 1 << 5
|
||||
GROUP_UPDATE_FLAG_MEMBER_LIST = 1 << 0
|
||||
GROUP_UPDATE_FLAG_MEMBER_STATS = 1 << 1
|
||||
GROUP_UPDATE_FLAG_MEMBER_ZONE = 1 << 2
|
||||
GROUP_UPDATE_FLAG_LEADERSHIP = 1 << 3
|
||||
GROUP_UPDATE_FLAG_OPTIONS = 1 << 4
|
||||
GROUP_UPDATE_FLAG_RAID_INFO = 1 << 5
|
||||
)
|
||||
|
||||
// Raid group constants
|
||||
@ -121,13 +121,13 @@ const (
|
||||
|
||||
// Group timing constants (in milliseconds)
|
||||
const (
|
||||
GROUP_UPDATE_INTERVAL = 1000 // 1 second
|
||||
GROUP_INVITE_TIMEOUT = 30000 // 30 seconds
|
||||
GROUP_BUFF_UPDATE_INTERVAL = 5000 // 5 seconds
|
||||
GROUP_UPDATE_INTERVAL = 1000 // 1 second
|
||||
GROUP_INVITE_TIMEOUT = 30000 // 30 seconds
|
||||
GROUP_BUFF_UPDATE_INTERVAL = 5000 // 5 seconds
|
||||
)
|
||||
|
||||
// Group validation constants
|
||||
const (
|
||||
MIN_GROUP_ID = 1
|
||||
MAX_GROUP_ID = 2147483647 // Max int32
|
||||
MIN_GROUP_ID = 1
|
||||
MAX_GROUP_ID = 2147483647 // Max int32
|
||||
)
|
@ -2,8 +2,6 @@ package groups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/entity"
|
||||
@ -92,13 +90,13 @@ func (g *Group) AddMember(member entity.Entity, isLeader bool) error {
|
||||
|
||||
// Create new group member info
|
||||
gmi := &GroupMemberInfo{
|
||||
GroupID: g.id,
|
||||
Name: member.GetName(),
|
||||
Leader: isLeader,
|
||||
Member: member,
|
||||
IsClient: member.IsPlayer(),
|
||||
JoinTime: time.Now(),
|
||||
LastUpdate: time.Now(),
|
||||
GroupID: g.id,
|
||||
Name: member.GetName(),
|
||||
Leader: isLeader,
|
||||
Member: member,
|
||||
IsClient: member.IsPlayer(),
|
||||
JoinTime: time.Now(),
|
||||
LastUpdate: time.Now(),
|
||||
}
|
||||
|
||||
// Update member stats from entity
|
||||
@ -154,29 +152,29 @@ func (g *Group) AddMemberFromPeer(name string, isLeader, isClient bool, classID
|
||||
|
||||
// Create new group member info for peer member
|
||||
gmi := &GroupMemberInfo{
|
||||
GroupID: g.id,
|
||||
Name: name,
|
||||
Zone: zoneName,
|
||||
HPCurrent: hpCur,
|
||||
HPMax: hpMax,
|
||||
PowerCurrent: powerCur,
|
||||
PowerMax: powerMax,
|
||||
LevelCurrent: levelCur,
|
||||
LevelMax: levelMax,
|
||||
RaceID: raceID,
|
||||
ClassID: classID,
|
||||
Leader: isLeader,
|
||||
IsClient: isClient,
|
||||
ZoneID: zoneID,
|
||||
InstanceID: instanceID,
|
||||
MentorTargetCharID: mentorTargetCharID,
|
||||
ClientPeerAddress: peerAddress,
|
||||
ClientPeerPort: peerPort,
|
||||
IsRaidLooter: isRaidLooter,
|
||||
Member: nil, // No local entity reference for peer members
|
||||
Client: nil, // No local client reference for peer members
|
||||
JoinTime: time.Now(),
|
||||
LastUpdate: time.Now(),
|
||||
GroupID: g.id,
|
||||
Name: name,
|
||||
Zone: zoneName,
|
||||
HPCurrent: hpCur,
|
||||
HPMax: hpMax,
|
||||
PowerCurrent: powerCur,
|
||||
PowerMax: powerMax,
|
||||
LevelCurrent: levelCur,
|
||||
LevelMax: levelMax,
|
||||
RaceID: raceID,
|
||||
ClassID: classID,
|
||||
Leader: isLeader,
|
||||
IsClient: isClient,
|
||||
ZoneID: zoneID,
|
||||
InstanceID: instanceID,
|
||||
MentorTargetCharID: mentorTargetCharID,
|
||||
ClientPeerAddress: peerAddress,
|
||||
ClientPeerPort: peerPort,
|
||||
IsRaidLooter: isRaidLooter,
|
||||
Member: nil, // No local entity reference for peer members
|
||||
Client: nil, // No local client reference for peer members
|
||||
JoinTime: time.Now(),
|
||||
LastUpdate: time.Now(),
|
||||
}
|
||||
|
||||
// Add to members list
|
||||
|
@ -2,6 +2,7 @@ package groups
|
||||
|
||||
import (
|
||||
"eq2emu/internal/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GroupAware interface for entities that can be part of groups
|
||||
|
@ -2,8 +2,6 @@ package groups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/entity"
|
||||
|
@ -22,21 +22,21 @@ type ServiceConfig struct {
|
||||
ManagerConfig GroupManagerConfig `json:"manager_config"`
|
||||
|
||||
// Service-specific settings
|
||||
AutoCreateGroups bool `json:"auto_create_groups"`
|
||||
AllowCrossZoneGroups bool `json:"allow_cross_zone_groups"`
|
||||
AllowBotMembers bool `json:"allow_bot_members"`
|
||||
AllowNPCMembers bool `json:"allow_npc_members"`
|
||||
MaxInviteDistance float32 `json:"max_invite_distance"`
|
||||
GroupLevelRange int8 `json:"group_level_range"`
|
||||
EnableGroupPvP bool `json:"enable_group_pvp"`
|
||||
EnableGroupBuffs bool `json:"enable_group_buffs"`
|
||||
LogLevel string `json:"log_level"`
|
||||
AutoCreateGroups bool `json:"auto_create_groups"`
|
||||
AllowCrossZoneGroups bool `json:"allow_cross_zone_groups"`
|
||||
AllowBotMembers bool `json:"allow_bot_members"`
|
||||
AllowNPCMembers bool `json:"allow_npc_members"`
|
||||
MaxInviteDistance float32 `json:"max_invite_distance"`
|
||||
GroupLevelRange int8 `json:"group_level_range"`
|
||||
EnableGroupPvP bool `json:"enable_group_pvp"`
|
||||
EnableGroupBuffs bool `json:"enable_group_buffs"`
|
||||
LogLevel string `json:"log_level"`
|
||||
|
||||
// Integration settings
|
||||
DatabaseEnabled bool `json:"database_enabled"`
|
||||
EventsEnabled bool `json:"events_enabled"`
|
||||
StatisticsEnabled bool `json:"statistics_enabled"`
|
||||
ValidationEnabled bool `json:"validation_enabled"`
|
||||
DatabaseEnabled bool `json:"database_enabled"`
|
||||
EventsEnabled bool `json:"events_enabled"`
|
||||
StatisticsEnabled bool `json:"statistics_enabled"`
|
||||
ValidationEnabled bool `json:"validation_enabled"`
|
||||
}
|
||||
|
||||
// DefaultServiceConfig returns default service configuration
|
||||
|
@ -9,208 +9,208 @@ import (
|
||||
|
||||
// GroupOptions holds group configuration settings
|
||||
type GroupOptions struct {
|
||||
LootMethod int8 `json:"loot_method"`
|
||||
LootItemsRarity int8 `json:"loot_items_rarity"`
|
||||
AutoSplit int8 `json:"auto_split"`
|
||||
DefaultYell int8 `json:"default_yell"`
|
||||
GroupLockMethod int8 `json:"group_lock_method"`
|
||||
GroupAutolock int8 `json:"group_autolock"`
|
||||
SoloAutolock int8 `json:"solo_autolock"`
|
||||
AutoLootMethod int8 `json:"auto_loot_method"`
|
||||
LastLootedIndex int8 `json:"last_looted_index"`
|
||||
LootMethod int8 `json:"loot_method"`
|
||||
LootItemsRarity int8 `json:"loot_items_rarity"`
|
||||
AutoSplit int8 `json:"auto_split"`
|
||||
DefaultYell int8 `json:"default_yell"`
|
||||
GroupLockMethod int8 `json:"group_lock_method"`
|
||||
GroupAutolock int8 `json:"group_autolock"`
|
||||
SoloAutolock int8 `json:"solo_autolock"`
|
||||
AutoLootMethod int8 `json:"auto_loot_method"`
|
||||
LastLootedIndex int8 `json:"last_looted_index"`
|
||||
}
|
||||
|
||||
// GroupMemberInfo contains all information about a group member
|
||||
type GroupMemberInfo struct {
|
||||
// Group and member identification
|
||||
GroupID int32 `json:"group_id"`
|
||||
Name string `json:"name"`
|
||||
Zone string `json:"zone"`
|
||||
GroupID int32 `json:"group_id"`
|
||||
Name string `json:"name"`
|
||||
Zone string `json:"zone"`
|
||||
|
||||
// Health and power stats
|
||||
HPCurrent int32 `json:"hp_current"`
|
||||
HPMax int32 `json:"hp_max"`
|
||||
PowerCurrent int32 `json:"power_current"`
|
||||
PowerMax int32 `json:"power_max"`
|
||||
HPCurrent int32 `json:"hp_current"`
|
||||
HPMax int32 `json:"hp_max"`
|
||||
PowerCurrent int32 `json:"power_current"`
|
||||
PowerMax int32 `json:"power_max"`
|
||||
|
||||
// Level and character info
|
||||
LevelCurrent int16 `json:"level_current"`
|
||||
LevelMax int16 `json:"level_max"`
|
||||
RaceID int8 `json:"race_id"`
|
||||
ClassID int8 `json:"class_id"`
|
||||
LevelCurrent int16 `json:"level_current"`
|
||||
LevelMax int16 `json:"level_max"`
|
||||
RaceID int8 `json:"race_id"`
|
||||
ClassID int8 `json:"class_id"`
|
||||
|
||||
// Group status
|
||||
Leader bool `json:"leader"`
|
||||
IsClient bool `json:"is_client"`
|
||||
IsRaidLooter bool `json:"is_raid_looter"`
|
||||
Leader bool `json:"leader"`
|
||||
IsClient bool `json:"is_client"`
|
||||
IsRaidLooter bool `json:"is_raid_looter"`
|
||||
|
||||
// Zone and instance info
|
||||
ZoneID int32 `json:"zone_id"`
|
||||
InstanceID int32 `json:"instance_id"`
|
||||
ZoneID int32 `json:"zone_id"`
|
||||
InstanceID int32 `json:"instance_id"`
|
||||
|
||||
// Mentoring
|
||||
MentorTargetCharID int32 `json:"mentor_target_char_id"`
|
||||
MentorTargetCharID int32 `json:"mentor_target_char_id"`
|
||||
|
||||
// Network info for cross-server groups
|
||||
ClientPeerAddress string `json:"client_peer_address"`
|
||||
ClientPeerPort int16 `json:"client_peer_port"`
|
||||
ClientPeerAddress string `json:"client_peer_address"`
|
||||
ClientPeerPort int16 `json:"client_peer_port"`
|
||||
|
||||
// Entity reference (local members only)
|
||||
Member entity.Entity `json:"-"`
|
||||
Member entity.Entity `json:"-"`
|
||||
|
||||
// Client reference (players only) - interface to avoid circular deps
|
||||
Client interface{} `json:"-"`
|
||||
Client interface{} `json:"-"`
|
||||
|
||||
// Timestamps
|
||||
JoinTime time.Time `json:"join_time"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
JoinTime time.Time `json:"join_time"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
}
|
||||
|
||||
// Group represents a player group
|
||||
type Group struct {
|
||||
// Group identification
|
||||
id int32
|
||||
id int32
|
||||
|
||||
// Group options and configuration
|
||||
options GroupOptions
|
||||
optionsMutex sync.RWMutex
|
||||
options GroupOptions
|
||||
optionsMutex sync.RWMutex
|
||||
|
||||
// Group members
|
||||
members []*GroupMemberInfo
|
||||
membersMutex sync.RWMutex
|
||||
members []*GroupMemberInfo
|
||||
membersMutex sync.RWMutex
|
||||
|
||||
// Raid functionality
|
||||
raidGroups []int32
|
||||
raidGroupsMutex sync.RWMutex
|
||||
raidGroups []int32
|
||||
raidGroupsMutex sync.RWMutex
|
||||
|
||||
// Group statistics
|
||||
createdTime time.Time
|
||||
lastActivity time.Time
|
||||
createdTime time.Time
|
||||
lastActivity time.Time
|
||||
|
||||
// Group status
|
||||
disbanded bool
|
||||
disbandMutex sync.RWMutex
|
||||
disbanded bool
|
||||
disbandMutex sync.RWMutex
|
||||
|
||||
// Communication channels
|
||||
messageQueue chan *GroupMessage
|
||||
updateQueue chan *GroupUpdate
|
||||
messageQueue chan *GroupMessage
|
||||
updateQueue chan *GroupUpdate
|
||||
|
||||
// Background processing
|
||||
stopChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
stopChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// GroupMessage represents a message sent to the group
|
||||
type GroupMessage struct {
|
||||
Type int8 `json:"type"`
|
||||
Channel int16 `json:"channel"`
|
||||
Message string `json:"message"`
|
||||
FromName string `json:"from_name"`
|
||||
Language int32 `json:"language"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
ExcludeClient interface{} `json:"-"`
|
||||
Type int8 `json:"type"`
|
||||
Channel int16 `json:"channel"`
|
||||
Message string `json:"message"`
|
||||
FromName string `json:"from_name"`
|
||||
Language int32 `json:"language"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
ExcludeClient interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// GroupUpdate represents a group update event
|
||||
type GroupUpdate struct {
|
||||
Type int8 `json:"type"`
|
||||
GroupID int32 `json:"group_id"`
|
||||
MemberInfo *GroupMemberInfo `json:"member_info,omitempty"`
|
||||
Options *GroupOptions `json:"options,omitempty"`
|
||||
RaidGroups []int32 `json:"raid_groups,omitempty"`
|
||||
ForceRaidUpdate bool `json:"force_raid_update"`
|
||||
ExcludeClient interface{} `json:"-"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Type int8 `json:"type"`
|
||||
GroupID int32 `json:"group_id"`
|
||||
MemberInfo *GroupMemberInfo `json:"member_info,omitempty"`
|
||||
Options *GroupOptions `json:"options,omitempty"`
|
||||
RaidGroups []int32 `json:"raid_groups,omitempty"`
|
||||
ForceRaidUpdate bool `json:"force_raid_update"`
|
||||
ExcludeClient interface{} `json:"-"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// GroupInvite represents a pending group invitation
|
||||
type GroupInvite struct {
|
||||
InviterName string `json:"inviter_name"`
|
||||
InviteeName string `json:"invitee_name"`
|
||||
GroupID int32 `json:"group_id"`
|
||||
IsRaidInvite bool `json:"is_raid_invite"`
|
||||
CreatedTime time.Time `json:"created_time"`
|
||||
ExpiresTime time.Time `json:"expires_time"`
|
||||
InviterName string `json:"inviter_name"`
|
||||
InviteeName string `json:"invitee_name"`
|
||||
GroupID int32 `json:"group_id"`
|
||||
IsRaidInvite bool `json:"is_raid_invite"`
|
||||
CreatedTime time.Time `json:"created_time"`
|
||||
ExpiresTime time.Time `json:"expires_time"`
|
||||
}
|
||||
|
||||
// GroupManager manages all player groups
|
||||
type GroupManager struct {
|
||||
// Group storage
|
||||
groups map[int32]*Group
|
||||
groupsMutex sync.RWMutex
|
||||
groups map[int32]*Group
|
||||
groupsMutex sync.RWMutex
|
||||
|
||||
// Group ID generation
|
||||
nextGroupID int32
|
||||
nextGroupIDMutex sync.Mutex
|
||||
nextGroupID int32
|
||||
nextGroupIDMutex sync.Mutex
|
||||
|
||||
// Pending invitations
|
||||
pendingInvites map[string]*GroupInvite
|
||||
raidPendingInvites map[string]*GroupInvite
|
||||
invitesMutex sync.RWMutex
|
||||
pendingInvites map[string]*GroupInvite
|
||||
raidPendingInvites map[string]*GroupInvite
|
||||
invitesMutex sync.RWMutex
|
||||
|
||||
// Event handlers
|
||||
eventHandlers []GroupEventHandler
|
||||
eventHandlersMutex sync.RWMutex
|
||||
eventHandlers []GroupEventHandler
|
||||
eventHandlersMutex sync.RWMutex
|
||||
|
||||
// Configuration
|
||||
config GroupManagerConfig
|
||||
config GroupManagerConfig
|
||||
|
||||
// Statistics
|
||||
stats GroupManagerStats
|
||||
statsMutex sync.RWMutex
|
||||
stats GroupManagerStats
|
||||
statsMutex sync.RWMutex
|
||||
|
||||
// Background processing
|
||||
stopChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
stopChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Integration interfaces
|
||||
database GroupDatabase
|
||||
packetHandler GroupPacketHandler
|
||||
validator GroupValidator
|
||||
notifier GroupNotifier
|
||||
database GroupDatabase
|
||||
packetHandler GroupPacketHandler
|
||||
validator GroupValidator
|
||||
notifier GroupNotifier
|
||||
}
|
||||
|
||||
// GroupManagerConfig holds configuration for the group manager
|
||||
type GroupManagerConfig struct {
|
||||
MaxGroups int32 `json:"max_groups"`
|
||||
MaxRaidGroups int32 `json:"max_raid_groups"`
|
||||
InviteTimeout time.Duration `json:"invite_timeout"`
|
||||
UpdateInterval time.Duration `json:"update_interval"`
|
||||
BuffUpdateInterval time.Duration `json:"buff_update_interval"`
|
||||
EnableCrossServer bool `json:"enable_cross_server"`
|
||||
EnableRaids bool `json:"enable_raids"`
|
||||
EnableQuestSharing bool `json:"enable_quest_sharing"`
|
||||
EnableAutoInvite bool `json:"enable_auto_invite"`
|
||||
EnableStatistics bool `json:"enable_statistics"`
|
||||
MaxGroups int32 `json:"max_groups"`
|
||||
MaxRaidGroups int32 `json:"max_raid_groups"`
|
||||
InviteTimeout time.Duration `json:"invite_timeout"`
|
||||
UpdateInterval time.Duration `json:"update_interval"`
|
||||
BuffUpdateInterval time.Duration `json:"buff_update_interval"`
|
||||
EnableCrossServer bool `json:"enable_cross_server"`
|
||||
EnableRaids bool `json:"enable_raids"`
|
||||
EnableQuestSharing bool `json:"enable_quest_sharing"`
|
||||
EnableAutoInvite bool `json:"enable_auto_invite"`
|
||||
EnableStatistics bool `json:"enable_statistics"`
|
||||
}
|
||||
|
||||
// GroupManagerStats holds statistics about group management
|
||||
type GroupManagerStats struct {
|
||||
TotalGroups int64 `json:"total_groups"`
|
||||
ActiveGroups int64 `json:"active_groups"`
|
||||
TotalRaids int64 `json:"total_raids"`
|
||||
ActiveRaids int64 `json:"active_raids"`
|
||||
TotalInvites int64 `json:"total_invites"`
|
||||
AcceptedInvites int64 `json:"accepted_invites"`
|
||||
DeclinedInvites int64 `json:"declined_invites"`
|
||||
ExpiredInvites int64 `json:"expired_invites"`
|
||||
AverageGroupSize float64 `json:"average_group_size"`
|
||||
AverageGroupDuration time.Duration `json:"average_group_duration"`
|
||||
LastStatsUpdate time.Time `json:"last_stats_update"`
|
||||
TotalGroups int64 `json:"total_groups"`
|
||||
ActiveGroups int64 `json:"active_groups"`
|
||||
TotalRaids int64 `json:"total_raids"`
|
||||
ActiveRaids int64 `json:"active_raids"`
|
||||
TotalInvites int64 `json:"total_invites"`
|
||||
AcceptedInvites int64 `json:"accepted_invites"`
|
||||
DeclinedInvites int64 `json:"declined_invites"`
|
||||
ExpiredInvites int64 `json:"expired_invites"`
|
||||
AverageGroupSize float64 `json:"average_group_size"`
|
||||
AverageGroupDuration time.Duration `json:"average_group_duration"`
|
||||
LastStatsUpdate time.Time `json:"last_stats_update"`
|
||||
}
|
||||
|
||||
// Default group options
|
||||
func DefaultGroupOptions() GroupOptions {
|
||||
return GroupOptions{
|
||||
LootMethod: LOOT_METHOD_ROUND_ROBIN,
|
||||
LootItemsRarity: LOOT_RARITY_COMMON,
|
||||
AutoSplit: AUTO_SPLIT_DISABLED,
|
||||
DefaultYell: DEFAULT_YELL_DISABLED,
|
||||
GroupLockMethod: LOCK_METHOD_OPEN,
|
||||
GroupAutolock: AUTO_LOCK_DISABLED,
|
||||
SoloAutolock: AUTO_LOCK_DISABLED,
|
||||
AutoLootMethod: AUTO_LOOT_DISABLED,
|
||||
LastLootedIndex: 0,
|
||||
LootMethod: LOOT_METHOD_ROUND_ROBIN,
|
||||
LootItemsRarity: LOOT_RARITY_COMMON,
|
||||
AutoSplit: AUTO_SPLIT_DISABLED,
|
||||
DefaultYell: DEFAULT_YELL_DISABLED,
|
||||
GroupLockMethod: LOCK_METHOD_OPEN,
|
||||
GroupAutolock: AUTO_LOCK_DISABLED,
|
||||
SoloAutolock: AUTO_LOCK_DISABLED,
|
||||
AutoLootMethod: AUTO_LOOT_DISABLED,
|
||||
LastLootedIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,7 +256,7 @@ func (go_opts *GroupOptions) Copy() GroupOptions {
|
||||
// IsValid checks if group options are valid
|
||||
func (go_opts *GroupOptions) IsValid() bool {
|
||||
return go_opts.LootMethod >= LOOT_METHOD_LEADER_ONLY && go_opts.LootMethod <= LOOT_METHOD_LOTTO &&
|
||||
go_opts.LootItemsRarity >= LOOT_RARITY_COMMON && go_opts.LootItemsRarity <= LOOT_RARITY_FABLED
|
||||
go_opts.LootItemsRarity >= LOOT_RARITY_COMMON && go_opts.LootItemsRarity <= LOOT_RARITY_FABLED
|
||||
}
|
||||
|
||||
// NewGroupMessage creates a new group message
|
||||
|
@ -2,55 +2,55 @@ package guilds
|
||||
|
||||
// Guild rank constants
|
||||
const (
|
||||
RankLeader = 0
|
||||
RankSeniorOfficer = 1
|
||||
RankOfficer = 2
|
||||
RankSeniorMember = 3
|
||||
RankMember = 4
|
||||
RankJuniorMember = 5
|
||||
RankInitiate = 6
|
||||
RankRecruit = 7
|
||||
RankLeader = 0
|
||||
RankSeniorOfficer = 1
|
||||
RankOfficer = 2
|
||||
RankSeniorMember = 3
|
||||
RankMember = 4
|
||||
RankJuniorMember = 5
|
||||
RankInitiate = 6
|
||||
RankRecruit = 7
|
||||
)
|
||||
|
||||
// Guild permission constants
|
||||
const (
|
||||
PermissionInvite = 0
|
||||
PermissionRemoveMember = 1
|
||||
PermissionPromoteMember = 2
|
||||
PermissionDemoteMember = 3
|
||||
PermissionChangeMOTD = 6
|
||||
PermissionChangePermissions = 7
|
||||
PermissionChangeRankNames = 8
|
||||
PermissionSeeOfficerNotes = 9
|
||||
PermissionEditOfficerNotes = 10
|
||||
PermissionSeeOfficerChat = 11
|
||||
PermissionSpeakInOfficerChat = 12
|
||||
PermissionSeeGuildChat = 13
|
||||
PermissionSpeakInGuildChat = 14
|
||||
PermissionEditPersonalNotes = 15
|
||||
PermissionEditPersonalNotesOthers = 16
|
||||
PermissionEditEventFilters = 17
|
||||
PermissionEditEvents = 18
|
||||
PermissionPurchaseStatusItems = 19
|
||||
PermissionDisplayGuildName = 20
|
||||
PermissionSendEmailToGuild = 21
|
||||
PermissionBank1SeeContents = 22
|
||||
PermissionBank2SeeContents = 23
|
||||
PermissionBank3SeeContents = 24
|
||||
PermissionBank4SeeContents = 25
|
||||
PermissionBank1Deposit = 26
|
||||
PermissionBank2Deposit = 27
|
||||
PermissionBank3Deposit = 28
|
||||
PermissionBank4Deposit = 29
|
||||
PermissionBank1Withdrawal = 30
|
||||
PermissionBank2Withdrawal = 31
|
||||
PermissionBank3Withdrawal = 32
|
||||
PermissionBank4Withdrawal = 33
|
||||
PermissionEditRecruitingSettings = 35
|
||||
PermissionMakeOthersRecruiters = 36
|
||||
PermissionSeeRecruitingSettings = 37
|
||||
PermissionAssignPoints = 43
|
||||
PermissionReceivePoints = 44
|
||||
PermissionInvite = 0
|
||||
PermissionRemoveMember = 1
|
||||
PermissionPromoteMember = 2
|
||||
PermissionDemoteMember = 3
|
||||
PermissionChangeMOTD = 6
|
||||
PermissionChangePermissions = 7
|
||||
PermissionChangeRankNames = 8
|
||||
PermissionSeeOfficerNotes = 9
|
||||
PermissionEditOfficerNotes = 10
|
||||
PermissionSeeOfficerChat = 11
|
||||
PermissionSpeakInOfficerChat = 12
|
||||
PermissionSeeGuildChat = 13
|
||||
PermissionSpeakInGuildChat = 14
|
||||
PermissionEditPersonalNotes = 15
|
||||
PermissionEditPersonalNotesOthers = 16
|
||||
PermissionEditEventFilters = 17
|
||||
PermissionEditEvents = 18
|
||||
PermissionPurchaseStatusItems = 19
|
||||
PermissionDisplayGuildName = 20
|
||||
PermissionSendEmailToGuild = 21
|
||||
PermissionBank1SeeContents = 22
|
||||
PermissionBank2SeeContents = 23
|
||||
PermissionBank3SeeContents = 24
|
||||
PermissionBank4SeeContents = 25
|
||||
PermissionBank1Deposit = 26
|
||||
PermissionBank2Deposit = 27
|
||||
PermissionBank3Deposit = 28
|
||||
PermissionBank4Deposit = 29
|
||||
PermissionBank1Withdrawal = 30
|
||||
PermissionBank2Withdrawal = 31
|
||||
PermissionBank3Withdrawal = 32
|
||||
PermissionBank4Withdrawal = 33
|
||||
PermissionEditRecruitingSettings = 35
|
||||
PermissionMakeOthersRecruiters = 36
|
||||
PermissionSeeRecruitingSettings = 37
|
||||
PermissionAssignPoints = 43
|
||||
PermissionReceivePoints = 44
|
||||
)
|
||||
|
||||
// Event filter categories
|
||||
@ -172,22 +172,22 @@ const (
|
||||
|
||||
// Recruiting description tags
|
||||
const (
|
||||
RecruitingDescTagNone = 0
|
||||
RecruitingDescTagGood = 1
|
||||
RecruitingDescTagEvil = 2
|
||||
RecruitingDescTagChatty = 3
|
||||
RecruitingDescTagOrganized = 4
|
||||
RecruitingDescTagRoleplay = 5
|
||||
RecruitingDescTagEnjoyQuests = 6
|
||||
RecruitingDescTagEnjoyRaids = 7
|
||||
RecruitingDescTagOddHours = 8
|
||||
RecruitingDescTagCrafterOriented = 9
|
||||
RecruitingDescTagFamilyFriendly = 10
|
||||
RecruitingDescTagMatureHumor = 11
|
||||
RecruitingDescTagInmatesRun = 12
|
||||
RecruitingDescTagVeryFunny = 13
|
||||
RecruitingDescTagHumorCausesPain = 14
|
||||
RecruitingDescTagSerious = 15
|
||||
RecruitingDescTagNone = 0
|
||||
RecruitingDescTagGood = 1
|
||||
RecruitingDescTagEvil = 2
|
||||
RecruitingDescTagChatty = 3
|
||||
RecruitingDescTagOrganized = 4
|
||||
RecruitingDescTagRoleplay = 5
|
||||
RecruitingDescTagEnjoyQuests = 6
|
||||
RecruitingDescTagEnjoyRaids = 7
|
||||
RecruitingDescTagOddHours = 8
|
||||
RecruitingDescTagCrafterOriented = 9
|
||||
RecruitingDescTagFamilyFriendly = 10
|
||||
RecruitingDescTagMatureHumor = 11
|
||||
RecruitingDescTagInmatesRun = 12
|
||||
RecruitingDescTagVeryFunny = 13
|
||||
RecruitingDescTagHumorCausesPain = 14
|
||||
RecruitingDescTagSerious = 15
|
||||
)
|
||||
|
||||
// Member flags
|
||||
@ -206,14 +206,14 @@ const (
|
||||
|
||||
// System limits
|
||||
const (
|
||||
MaxGuildLevel = 80
|
||||
MaxPointHistory = 50
|
||||
MaxEvents = 500
|
||||
MaxLockedEvents = 200
|
||||
MaxGuildNameLength = 64
|
||||
MaxMOTDLength = 256
|
||||
MaxMemberNameLength = 64
|
||||
MaxBankNameLength = 64
|
||||
MaxGuildLevel = 80
|
||||
MaxPointHistory = 50
|
||||
MaxEvents = 500
|
||||
MaxLockedEvents = 200
|
||||
MaxGuildNameLength = 64
|
||||
MaxMOTDLength = 256
|
||||
MaxMemberNameLength = 64
|
||||
MaxBankNameLength = 64
|
||||
MaxRecruitingDescLength = 512
|
||||
)
|
||||
|
||||
|
@ -573,9 +573,9 @@ func (g *Guild) AddNewGuildMember(characterID int32, invitedBy string, joinDate
|
||||
}
|
||||
|
||||
member := &GuildMember{
|
||||
CharacterID: characterID,
|
||||
Rank: rank,
|
||||
JoinDate: joinDate,
|
||||
CharacterID: characterID,
|
||||
Rank: rank,
|
||||
JoinDate: joinDate,
|
||||
PointHistory: make([]PointHistory, 0),
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
package guilds
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GuildDatabase defines database operations for guilds
|
||||
type GuildDatabase interface {
|
||||
@ -193,15 +196,15 @@ type LogHandler interface {
|
||||
|
||||
// PlayerInfo contains basic player information
|
||||
type PlayerInfo struct {
|
||||
CharacterID int32 `json:"character_id"`
|
||||
CharacterName string `json:"character_name"`
|
||||
AccountID int32 `json:"account_id"`
|
||||
AdventureLevel int8 `json:"adventure_level"`
|
||||
AdventureClass int8 `json:"adventure_class"`
|
||||
TradeskillLevel int8 `json:"tradeskill_level"`
|
||||
TradeskillClass int8 `json:"tradeskill_class"`
|
||||
Zone string `json:"zone"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
CharacterID int32 `json:"character_id"`
|
||||
CharacterName string `json:"character_name"`
|
||||
AccountID int32 `json:"account_id"`
|
||||
AdventureLevel int8 `json:"adventure_level"`
|
||||
AdventureClass int8 `json:"adventure_class"`
|
||||
TradeskillLevel int8 `json:"tradeskill_level"`
|
||||
TradeskillClass int8 `json:"tradeskill_class"`
|
||||
Zone string `json:"zone"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
}
|
||||
|
||||
@ -334,9 +337,9 @@ type BankManager interface {
|
||||
|
||||
// BankItem represents an item in the guild bank
|
||||
type BankItem struct {
|
||||
Slot int `json:"slot"`
|
||||
ItemID int32 `json:"item_id"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
DepositorID int32 `json:"depositor_id"`
|
||||
Slot int `json:"slot"`
|
||||
ItemID int32 `json:"item_id"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
DepositorID int32 `json:"depositor_id"`
|
||||
DepositDate time.Time `json:"deposit_date"`
|
||||
}
|
@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -632,24 +631,24 @@ func (gm *GuildManager) loadGuildFromData(ctx context.Context, data GuildData) (
|
||||
CharacterID: md.CharacterID,
|
||||
AccountID: md.AccountID,
|
||||
RecruiterID: md.RecruiterID,
|
||||
Name: md.Name,
|
||||
GuildStatus: md.GuildStatus,
|
||||
Points: md.Points,
|
||||
AdventureClass: md.AdventureClass,
|
||||
AdventureLevel: md.AdventureLevel,
|
||||
TradeskillClass: md.TradeskillClass,
|
||||
TradeskillLevel: md.TradeskillLevel,
|
||||
Rank: md.Rank,
|
||||
MemberFlags: md.MemberFlags,
|
||||
Zone: md.Zone,
|
||||
JoinDate: md.JoinDate,
|
||||
LastLoginDate: md.LastLoginDate,
|
||||
Note: md.Note,
|
||||
OfficerNote: md.OfficerNote,
|
||||
RecruiterDescription: md.RecruiterDescription,
|
||||
RecruiterPictureData: md.RecruiterPictureData,
|
||||
Name: md.Name,
|
||||
GuildStatus: md.GuildStatus,
|
||||
Points: md.Points,
|
||||
AdventureClass: md.AdventureClass,
|
||||
AdventureLevel: md.AdventureLevel,
|
||||
TradeskillClass: md.TradeskillClass,
|
||||
TradeskillLevel: md.TradeskillLevel,
|
||||
Rank: md.Rank,
|
||||
MemberFlags: md.MemberFlags,
|
||||
Zone: md.Zone,
|
||||
JoinDate: md.JoinDate,
|
||||
LastLoginDate: md.LastLoginDate,
|
||||
Note: md.Note,
|
||||
OfficerNote: md.OfficerNote,
|
||||
RecruiterDescription: md.RecruiterDescription,
|
||||
RecruiterPictureData: md.RecruiterPictureData,
|
||||
RecruitingShowAdventureClass: md.RecruitingShowAdventureClass,
|
||||
PointHistory: make([]PointHistory, 0),
|
||||
PointHistory: make([]PointHistory, 0),
|
||||
}
|
||||
|
||||
// Load point history
|
||||
|
@ -8,11 +8,11 @@ import (
|
||||
func NewGuildMember(characterID int32, name string, rank int8) *GuildMember {
|
||||
return &GuildMember{
|
||||
CharacterID: characterID,
|
||||
Name: name,
|
||||
Rank: rank,
|
||||
JoinDate: time.Now(),
|
||||
LastLoginDate: time.Now(),
|
||||
PointHistory: make([]PointHistory, 0),
|
||||
Name: name,
|
||||
Rank: rank,
|
||||
JoinDate: time.Now(),
|
||||
LastLoginDate: time.Now(),
|
||||
PointHistory: make([]PointHistory, 0),
|
||||
RecruitingShowAdventureClass: 1,
|
||||
}
|
||||
}
|
||||
@ -388,12 +388,12 @@ func (gm *GuildMember) GetRecruiterInfo(isOnline bool) RecruiterInfo {
|
||||
|
||||
return RecruiterInfo{
|
||||
CharacterID: gm.CharacterID,
|
||||
Name: gm.Name,
|
||||
Description: gm.RecruiterDescription,
|
||||
PictureData: gm.GetRecruiterPictureData(), // This will make a copy
|
||||
Name: gm.Name,
|
||||
Description: gm.RecruiterDescription,
|
||||
PictureData: gm.GetRecruiterPictureData(), // This will make a copy
|
||||
ShowAdventureClass: gm.RecruitingShowAdventureClass != 0,
|
||||
AdventureClass: gm.AdventureClass,
|
||||
IsOnline: isOnline,
|
||||
AdventureClass: gm.AdventureClass,
|
||||
IsOnline: isOnline,
|
||||
}
|
||||
}
|
||||
|
||||
@ -458,23 +458,23 @@ func (gm *GuildMember) Copy() *GuildMember {
|
||||
CharacterID: gm.CharacterID,
|
||||
AccountID: gm.AccountID,
|
||||
RecruiterID: gm.RecruiterID,
|
||||
Name: gm.Name,
|
||||
GuildStatus: gm.GuildStatus,
|
||||
Points: gm.Points,
|
||||
AdventureClass: gm.AdventureClass,
|
||||
AdventureLevel: gm.AdventureLevel,
|
||||
TradeskillClass: gm.TradeskillClass,
|
||||
TradeskillLevel: gm.TradeskillLevel,
|
||||
Rank: gm.Rank,
|
||||
MemberFlags: gm.MemberFlags,
|
||||
Zone: gm.Zone,
|
||||
JoinDate: gm.JoinDate,
|
||||
LastLoginDate: gm.LastLoginDate,
|
||||
Note: gm.Note,
|
||||
OfficerNote: gm.OfficerNote,
|
||||
RecruiterDescription: gm.RecruiterDescription,
|
||||
Name: gm.Name,
|
||||
GuildStatus: gm.GuildStatus,
|
||||
Points: gm.Points,
|
||||
AdventureClass: gm.AdventureClass,
|
||||
AdventureLevel: gm.AdventureLevel,
|
||||
TradeskillClass: gm.TradeskillClass,
|
||||
TradeskillLevel: gm.TradeskillLevel,
|
||||
Rank: gm.Rank,
|
||||
MemberFlags: gm.MemberFlags,
|
||||
Zone: gm.Zone,
|
||||
JoinDate: gm.JoinDate,
|
||||
LastLoginDate: gm.LastLoginDate,
|
||||
Note: gm.Note,
|
||||
OfficerNote: gm.OfficerNote,
|
||||
RecruiterDescription: gm.RecruiterDescription,
|
||||
RecruitingShowAdventureClass: gm.RecruitingShowAdventureClass,
|
||||
PointHistory: make([]PointHistory, len(gm.PointHistory)),
|
||||
PointHistory: make([]PointHistory, len(gm.PointHistory)),
|
||||
}
|
||||
|
||||
// Deep copy point history
|
||||
|
@ -17,27 +17,27 @@ type PointHistory struct {
|
||||
// GuildMember represents a member of a guild
|
||||
type GuildMember struct {
|
||||
mu sync.RWMutex
|
||||
CharacterID int32 `json:"character_id" db:"character_id"`
|
||||
AccountID int32 `json:"account_id" db:"account_id"`
|
||||
RecruiterID int32 `json:"recruiter_id" db:"recruiter_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
GuildStatus int32 `json:"guild_status" db:"guild_status"`
|
||||
Points float64 `json:"points" db:"points"`
|
||||
AdventureClass int8 `json:"adventure_class" db:"adventure_class"`
|
||||
AdventureLevel int8 `json:"adventure_level" db:"adventure_level"`
|
||||
TradeskillClass int8 `json:"tradeskill_class" db:"tradeskill_class"`
|
||||
TradeskillLevel int8 `json:"tradeskill_level" db:"tradeskill_level"`
|
||||
Rank int8 `json:"rank" db:"rank"`
|
||||
MemberFlags int8 `json:"member_flags" db:"member_flags"`
|
||||
Zone string `json:"zone" db:"zone"`
|
||||
JoinDate time.Time `json:"join_date" db:"join_date"`
|
||||
LastLoginDate time.Time `json:"last_login_date" db:"last_login_date"`
|
||||
Note string `json:"note" db:"note"`
|
||||
OfficerNote string `json:"officer_note" db:"officer_note"`
|
||||
RecruiterDescription string `json:"recruiter_description" db:"recruiter_description"`
|
||||
RecruiterPictureData []byte `json:"recruiter_picture_data" db:"recruiter_picture_data"`
|
||||
RecruitingShowAdventureClass int8 `json:"recruiting_show_adventure_class" db:"recruiting_show_adventure_class"`
|
||||
PointHistory []PointHistory `json:"point_history" db:"-"`
|
||||
CharacterID int32 `json:"character_id" db:"character_id"`
|
||||
AccountID int32 `json:"account_id" db:"account_id"`
|
||||
RecruiterID int32 `json:"recruiter_id" db:"recruiter_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
GuildStatus int32 `json:"guild_status" db:"guild_status"`
|
||||
Points float64 `json:"points" db:"points"`
|
||||
AdventureClass int8 `json:"adventure_class" db:"adventure_class"`
|
||||
AdventureLevel int8 `json:"adventure_level" db:"adventure_level"`
|
||||
TradeskillClass int8 `json:"tradeskill_class" db:"tradeskill_class"`
|
||||
TradeskillLevel int8 `json:"tradeskill_level" db:"tradeskill_level"`
|
||||
Rank int8 `json:"rank" db:"rank"`
|
||||
MemberFlags int8 `json:"member_flags" db:"member_flags"`
|
||||
Zone string `json:"zone" db:"zone"`
|
||||
JoinDate time.Time `json:"join_date" db:"join_date"`
|
||||
LastLoginDate time.Time `json:"last_login_date" db:"last_login_date"`
|
||||
Note string `json:"note" db:"note"`
|
||||
OfficerNote string `json:"officer_note" db:"officer_note"`
|
||||
RecruiterDescription string `json:"recruiter_description" db:"recruiter_description"`
|
||||
RecruiterPictureData []byte `json:"recruiter_picture_data" db:"recruiter_picture_data"`
|
||||
RecruitingShowAdventureClass int8 `json:"recruiting_show_adventure_class" db:"recruiting_show_adventure_class"`
|
||||
PointHistory []PointHistory `json:"point_history" db:"-"`
|
||||
}
|
||||
|
||||
// GuildEvent represents an event in the guild's history
|
||||
@ -60,45 +60,45 @@ type GuildBankEvent struct {
|
||||
|
||||
// Bank represents a guild bank with its event history
|
||||
type Bank struct {
|
||||
Name string `json:"name" db:"name"`
|
||||
Events []GuildBankEvent `json:"events" db:"-"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Events []GuildBankEvent `json:"events" db:"-"`
|
||||
}
|
||||
|
||||
// Guild represents a guild with all its properties and members
|
||||
type Guild struct {
|
||||
mu sync.RWMutex
|
||||
id int32
|
||||
name string
|
||||
level int8
|
||||
formedDate time.Time
|
||||
motd string
|
||||
expCurrent int64
|
||||
expToNextLevel int64
|
||||
recruitingShortDesc string
|
||||
recruitingFullDesc string
|
||||
recruitingMinLevel int8
|
||||
recruitingPlayStyle int8
|
||||
members map[int32]*GuildMember
|
||||
guildEvents []GuildEvent
|
||||
permissions map[int8]map[int8]int8 // rank -> permission -> value
|
||||
eventFilters map[int8]map[int8]int8 // event_id -> category -> value
|
||||
recruitingFlags map[int8]int8
|
||||
recruitingDescTags map[int8]int8
|
||||
ranks map[int8]string // rank -> name
|
||||
banks [4]Bank
|
||||
mu sync.RWMutex
|
||||
id int32
|
||||
name string
|
||||
level int8
|
||||
formedDate time.Time
|
||||
motd string
|
||||
expCurrent int64
|
||||
expToNextLevel int64
|
||||
recruitingShortDesc string
|
||||
recruitingFullDesc string
|
||||
recruitingMinLevel int8
|
||||
recruitingPlayStyle int8
|
||||
members map[int32]*GuildMember
|
||||
guildEvents []GuildEvent
|
||||
permissions map[int8]map[int8]int8 // rank -> permission -> value
|
||||
eventFilters map[int8]map[int8]int8 // event_id -> category -> value
|
||||
recruitingFlags map[int8]int8
|
||||
recruitingDescTags map[int8]int8
|
||||
ranks map[int8]string // rank -> name
|
||||
banks [4]Bank
|
||||
|
||||
// Save flags
|
||||
saveNeeded bool
|
||||
memberSaveNeeded bool
|
||||
eventsSaveNeeded bool
|
||||
ranksSaveNeeded bool
|
||||
eventFiltersSaveNeeded bool
|
||||
pointsHistorySaveNeeded bool
|
||||
recruitingSaveNeeded bool
|
||||
saveNeeded bool
|
||||
memberSaveNeeded bool
|
||||
eventsSaveNeeded bool
|
||||
ranksSaveNeeded bool
|
||||
eventFiltersSaveNeeded bool
|
||||
pointsHistorySaveNeeded bool
|
||||
recruitingSaveNeeded bool
|
||||
|
||||
// Tracking
|
||||
nextEventID int64
|
||||
lastModified time.Time
|
||||
nextEventID int64
|
||||
lastModified time.Time
|
||||
}
|
||||
|
||||
// GuildData represents guild data for database operations
|
||||
@ -286,38 +286,38 @@ type GuildEventInfo struct {
|
||||
|
||||
// GuildSearchCriteria represents guild search parameters
|
||||
type GuildSearchCriteria struct {
|
||||
NamePattern string `json:"name_pattern"`
|
||||
MinLevel int8 `json:"min_level"`
|
||||
MaxLevel int8 `json:"max_level"`
|
||||
MinMembers int `json:"min_members"`
|
||||
MaxMembers int `json:"max_members"`
|
||||
RecruitingOnly bool `json:"recruiting_only"`
|
||||
PlayStyle int8 `json:"play_style"`
|
||||
RequiredFlags []int8 `json:"required_flags"`
|
||||
RequiredDescTags []int8 `json:"required_desc_tags"`
|
||||
ExcludedDescTags []int8 `json:"excluded_desc_tags"`
|
||||
NamePattern string `json:"name_pattern"`
|
||||
MinLevel int8 `json:"min_level"`
|
||||
MaxLevel int8 `json:"max_level"`
|
||||
MinMembers int `json:"min_members"`
|
||||
MaxMembers int `json:"max_members"`
|
||||
RecruitingOnly bool `json:"recruiting_only"`
|
||||
PlayStyle int8 `json:"play_style"`
|
||||
RequiredFlags []int8 `json:"required_flags"`
|
||||
RequiredDescTags []int8 `json:"required_desc_tags"`
|
||||
ExcludedDescTags []int8 `json:"excluded_desc_tags"`
|
||||
}
|
||||
|
||||
// RecruitingInfo provides detailed recruiting information
|
||||
type RecruitingInfo struct {
|
||||
GuildID int32 `json:"guild_id"`
|
||||
GuildName string `json:"guild_name"`
|
||||
ShortDesc string `json:"short_desc"`
|
||||
FullDesc string `json:"full_desc"`
|
||||
MinLevel int8 `json:"min_level"`
|
||||
PlayStyle int8 `json:"play_style"`
|
||||
Flags map[int8]int8 `json:"flags"`
|
||||
DescTags map[int8]int8 `json:"desc_tags"`
|
||||
Recruiters []RecruiterInfo `json:"recruiters"`
|
||||
GuildID int32 `json:"guild_id"`
|
||||
GuildName string `json:"guild_name"`
|
||||
ShortDesc string `json:"short_desc"`
|
||||
FullDesc string `json:"full_desc"`
|
||||
MinLevel int8 `json:"min_level"`
|
||||
PlayStyle int8 `json:"play_style"`
|
||||
Flags map[int8]int8 `json:"flags"`
|
||||
DescTags map[int8]int8 `json:"desc_tags"`
|
||||
Recruiters []RecruiterInfo `json:"recruiters"`
|
||||
}
|
||||
|
||||
// RecruiterInfo provides recruiter information
|
||||
type RecruiterInfo struct {
|
||||
CharacterID int32 `json:"character_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
PictureData []byte `json:"picture_data"`
|
||||
ShowAdventureClass bool `json:"show_adventure_class"`
|
||||
AdventureClass int8 `json:"adventure_class"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
CharacterID int32 `json:"character_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
PictureData []byte `json:"picture_data"`
|
||||
ShowAdventureClass bool `json:"show_adventure_class"`
|
||||
AdventureClass int8 `json:"adventure_class"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
}
|
@ -68,10 +68,10 @@ const (
|
||||
MaxDatabaseRetries = 3
|
||||
|
||||
// Memory management
|
||||
DefaultHOPoolSize = 100
|
||||
DefaultStarterCache = 500
|
||||
DefaultWheelCache = 1000
|
||||
MaxHOHistoryEntries = 50
|
||||
DefaultHOPoolSize = 100
|
||||
DefaultStarterCache = 500
|
||||
DefaultWheelCache = 1000
|
||||
MaxHOHistoryEntries = 50
|
||||
)
|
||||
|
||||
// Heroic Opportunity Event Types for logging and statistics
|
||||
|
@ -3,7 +3,6 @@ package heroic_ops
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -154,12 +154,12 @@ type CacheManager interface {
|
||||
|
||||
// EncounterInfo contains encounter details
|
||||
type EncounterInfo struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Participants []int32 `json:"participants"`
|
||||
IsActive bool `json:"is_active"`
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Participants []int32 `json:"participants"`
|
||||
IsActive bool `json:"is_active"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
Level int16 `json:"level"`
|
||||
Level int16 `json:"level"`
|
||||
}
|
||||
|
||||
// PlayerInfo contains player details needed for HO system
|
||||
|
@ -3,7 +3,6 @@ package heroic_ops
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -3,9 +3,7 @@ package heroic_ops
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewMasterHeroicOPList creates a new master heroic opportunity list
|
||||
|
@ -7,54 +7,54 @@ import (
|
||||
|
||||
// HeroicOPStarter represents a starter chain for heroic opportunities
|
||||
type HeroicOPStarter struct {
|
||||
mu sync.RWMutex
|
||||
ID int32 `json:"id"` // Unique identifier for this starter
|
||||
StartClass int8 `json:"start_class"` // Class that can initiate this starter (0 = any)
|
||||
StarterIcon int16 `json:"starter_icon"` // Icon displayed for the starter
|
||||
Abilities [6]int16 `json:"abilities"` // Array of ability icons in sequence
|
||||
Name string `json:"name"` // Display name for this starter
|
||||
Description string `json:"description"` // Description text
|
||||
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
|
||||
mu sync.RWMutex
|
||||
ID int32 `json:"id"` // Unique identifier for this starter
|
||||
StartClass int8 `json:"start_class"` // Class that can initiate this starter (0 = any)
|
||||
StarterIcon int16 `json:"starter_icon"` // Icon displayed for the starter
|
||||
Abilities [6]int16 `json:"abilities"` // Array of ability icons in sequence
|
||||
Name string `json:"name"` // Display name for this starter
|
||||
Description string `json:"description"` // Description text
|
||||
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
|
||||
}
|
||||
|
||||
// HeroicOPWheel represents the wheel phase of a heroic opportunity
|
||||
type HeroicOPWheel struct {
|
||||
mu sync.RWMutex
|
||||
ID int32 `json:"id"` // Unique identifier for this wheel
|
||||
StarterLinkID int32 `json:"starter_link_id"` // ID of the starter this wheel belongs to
|
||||
Order int8 `json:"order"` // 0 = unordered, 1+ = ordered
|
||||
ShiftIcon int16 `json:"shift_icon"` // Icon that can shift/change the wheel
|
||||
Chance float32 `json:"chance"` // Probability factor for selecting this wheel
|
||||
Abilities [6]int16 `json:"abilities"` // Array of ability icons for the wheel
|
||||
SpellID int32 `json:"spell_id"` // Spell cast when HO completes successfully
|
||||
Name string `json:"name"` // Display name for this wheel
|
||||
Description string `json:"description"` // Description text
|
||||
RequiredPlayers int8 `json:"required_players"` // Minimum players required
|
||||
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
|
||||
mu sync.RWMutex
|
||||
ID int32 `json:"id"` // Unique identifier for this wheel
|
||||
StarterLinkID int32 `json:"starter_link_id"` // ID of the starter this wheel belongs to
|
||||
Order int8 `json:"order"` // 0 = unordered, 1+ = ordered
|
||||
ShiftIcon int16 `json:"shift_icon"` // Icon that can shift/change the wheel
|
||||
Chance float32 `json:"chance"` // Probability factor for selecting this wheel
|
||||
Abilities [6]int16 `json:"abilities"` // Array of ability icons for the wheel
|
||||
SpellID int32 `json:"spell_id"` // Spell cast when HO completes successfully
|
||||
Name string `json:"name"` // Display name for this wheel
|
||||
Description string `json:"description"` // Description text
|
||||
RequiredPlayers int8 `json:"required_players"` // Minimum players required
|
||||
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
|
||||
}
|
||||
|
||||
// HeroicOP represents an active heroic opportunity instance
|
||||
type HeroicOP struct {
|
||||
mu sync.RWMutex
|
||||
ID int64 `json:"id"` // Unique instance ID
|
||||
EncounterID int32 `json:"encounter_id"` // Encounter this HO belongs to
|
||||
StarterID int32 `json:"starter_id"` // ID of the completed starter
|
||||
WheelID int32 `json:"wheel_id"` // ID of the active wheel
|
||||
State int8 `json:"state"` // Current HO state
|
||||
StartTime time.Time `json:"start_time"` // When the HO started
|
||||
WheelStartTime time.Time `json:"wheel_start_time"` // When wheel phase started
|
||||
TimeRemaining int32 `json:"time_remaining"` // Milliseconds remaining
|
||||
TotalTime int32 `json:"total_time"` // Total time allocated (ms)
|
||||
Complete int8 `json:"complete"` // Completion status (0/1)
|
||||
Countered [6]int8 `json:"countered"` // Which wheel abilities are completed
|
||||
ShiftUsed int8 `json:"shift_used"` // Whether shift has been used
|
||||
StarterProgress int8 `json:"starter_progress"` // Current position in starter chain
|
||||
Participants map[int32]bool `json:"participants"` // Character IDs that participated
|
||||
CurrentStarters []int32 `json:"current_starters"` // Active starter IDs during chain phase
|
||||
CompletedBy int32 `json:"completed_by"` // Character ID that completed the HO
|
||||
SpellName string `json:"spell_name"` // Name of completion spell
|
||||
SpellDescription string `json:"spell_description"` // Description of completion spell
|
||||
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
|
||||
ID int64 `json:"id"` // Unique instance ID
|
||||
EncounterID int32 `json:"encounter_id"` // Encounter this HO belongs to
|
||||
StarterID int32 `json:"starter_id"` // ID of the completed starter
|
||||
WheelID int32 `json:"wheel_id"` // ID of the active wheel
|
||||
State int8 `json:"state"` // Current HO state
|
||||
StartTime time.Time `json:"start_time"` // When the HO started
|
||||
WheelStartTime time.Time `json:"wheel_start_time"` // When wheel phase started
|
||||
TimeRemaining int32 `json:"time_remaining"` // Milliseconds remaining
|
||||
TotalTime int32 `json:"total_time"` // Total time allocated (ms)
|
||||
Complete int8 `json:"complete"` // Completion status (0/1)
|
||||
Countered [6]int8 `json:"countered"` // Which wheel abilities are completed
|
||||
ShiftUsed int8 `json:"shift_used"` // Whether shift has been used
|
||||
StarterProgress int8 `json:"starter_progress"` // Current position in starter chain
|
||||
Participants map[int32]bool `json:"participants"` // Character IDs that participated
|
||||
CurrentStarters []int32 `json:"current_starters"` // Active starter IDs during chain phase
|
||||
CompletedBy int32 `json:"completed_by"` // Character ID that completed the HO
|
||||
SpellName string `json:"spell_name"` // Name of completion spell
|
||||
SpellDescription string `json:"spell_description"` // Description of completion spell
|
||||
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
|
||||
}
|
||||
|
||||
// HeroicOPProgress tracks progress during starter chain phase
|
||||
@ -66,28 +66,28 @@ type HeroicOPProgress struct {
|
||||
|
||||
// HeroicOPData represents database record structure
|
||||
type HeroicOPData struct {
|
||||
ID int32 `json:"id"`
|
||||
HOType string `json:"ho_type"` // "Starter" or "Wheel"
|
||||
StarterClass int8 `json:"starter_class"` // For starters
|
||||
StarterIcon int16 `json:"starter_icon"` // For starters
|
||||
StarterLinkID int32 `json:"starter_link_id"` // For wheels
|
||||
ChainOrder int8 `json:"chain_order"` // For wheels
|
||||
ShiftIcon int16 `json:"shift_icon"` // For wheels
|
||||
SpellID int32 `json:"spell_id"` // For wheels
|
||||
Chance float32 `json:"chance"` // For wheels
|
||||
Ability1 int16 `json:"ability1"`
|
||||
Ability2 int16 `json:"ability2"`
|
||||
Ability3 int16 `json:"ability3"`
|
||||
Ability4 int16 `json:"ability4"`
|
||||
Ability5 int16 `json:"ability5"`
|
||||
Ability6 int16 `json:"ability6"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
ID int32 `json:"id"`
|
||||
HOType string `json:"ho_type"` // "Starter" or "Wheel"
|
||||
StarterClass int8 `json:"starter_class"` // For starters
|
||||
StarterIcon int16 `json:"starter_icon"` // For starters
|
||||
StarterLinkID int32 `json:"starter_link_id"` // For wheels
|
||||
ChainOrder int8 `json:"chain_order"` // For wheels
|
||||
ShiftIcon int16 `json:"shift_icon"` // For wheels
|
||||
SpellID int32 `json:"spell_id"` // For wheels
|
||||
Chance float32 `json:"chance"` // For wheels
|
||||
Ability1 int16 `json:"ability1"`
|
||||
Ability2 int16 `json:"ability2"`
|
||||
Ability3 int16 `json:"ability3"`
|
||||
Ability4 int16 `json:"ability4"`
|
||||
Ability5 int16 `json:"ability5"`
|
||||
Ability6 int16 `json:"ability6"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// MasterHeroicOPList manages all heroic opportunity configurations
|
||||
type MasterHeroicOPList struct {
|
||||
mu sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
// Structure: map[class]map[starter_id][]wheel
|
||||
starters map[int8]map[int32]*HeroicOPStarter
|
||||
wheels map[int32][]*HeroicOPWheel // starter_id -> wheels
|
||||
@ -97,13 +97,13 @@ type MasterHeroicOPList struct {
|
||||
|
||||
// HeroicOPManager manages active heroic opportunity instances
|
||||
type HeroicOPManager struct {
|
||||
mu sync.RWMutex
|
||||
activeHOs map[int64]*HeroicOP // instance_id -> HO
|
||||
encounterHOs map[int32][]*HeroicOP // encounter_id -> HOs
|
||||
masterList *MasterHeroicOPList
|
||||
database HeroicOPDatabase
|
||||
eventHandler HeroicOPEventHandler
|
||||
logger LogHandler
|
||||
mu sync.RWMutex
|
||||
activeHOs map[int64]*HeroicOP // instance_id -> HO
|
||||
encounterHOs map[int32][]*HeroicOP // encounter_id -> HOs
|
||||
masterList *MasterHeroicOPList
|
||||
database HeroicOPDatabase
|
||||
eventHandler HeroicOPEventHandler
|
||||
logger LogHandler
|
||||
nextInstanceID int64
|
||||
// Configuration
|
||||
defaultWheelTimer int32 // milliseconds
|
||||
@ -122,17 +122,17 @@ type SpellInfo struct {
|
||||
|
||||
// HeroicOPStatistics tracks system usage statistics
|
||||
type HeroicOPStatistics struct {
|
||||
TotalHOsStarted int64 `json:"total_hos_started"`
|
||||
TotalHOsCompleted int64 `json:"total_hos_completed"`
|
||||
TotalHOsFailed int64 `json:"total_hos_failed"`
|
||||
TotalHOsTimedOut int64 `json:"total_hos_timed_out"`
|
||||
AverageCompletionTime float64 `json:"average_completion_time"` // seconds
|
||||
MostUsedStarter int32 `json:"most_used_starter"`
|
||||
MostUsedWheel int32 `json:"most_used_wheel"`
|
||||
SuccessRate float64 `json:"success_rate"` // percentage
|
||||
ShiftUsageRate float64 `json:"shift_usage_rate"` // percentage
|
||||
ActiveHOCount int `json:"active_ho_count"`
|
||||
ParticipationStats map[int32]int64 `json:"participation_stats"` // character_id -> HO count
|
||||
TotalHOsStarted int64 `json:"total_hos_started"`
|
||||
TotalHOsCompleted int64 `json:"total_hos_completed"`
|
||||
TotalHOsFailed int64 `json:"total_hos_failed"`
|
||||
TotalHOsTimedOut int64 `json:"total_hos_timed_out"`
|
||||
AverageCompletionTime float64 `json:"average_completion_time"` // seconds
|
||||
MostUsedStarter int32 `json:"most_used_starter"`
|
||||
MostUsedWheel int32 `json:"most_used_wheel"`
|
||||
SuccessRate float64 `json:"success_rate"` // percentage
|
||||
ShiftUsageRate float64 `json:"shift_usage_rate"` // percentage
|
||||
ActiveHOCount int `json:"active_ho_count"`
|
||||
ParticipationStats map[int32]int64 `json:"participation_stats"` // character_id -> HO count
|
||||
}
|
||||
|
||||
// HeroicOPSearchCriteria for searching heroic opportunities
|
||||
@ -160,37 +160,37 @@ type HeroicOPEvent struct {
|
||||
|
||||
// PacketData represents data sent to client for HO display
|
||||
type PacketData struct {
|
||||
SpellName string `json:"spell_name"`
|
||||
SpellDescription string `json:"spell_description"`
|
||||
TimeRemaining int32 `json:"time_remaining"` // milliseconds
|
||||
TotalTime int32 `json:"total_time"` // milliseconds
|
||||
Abilities [6]int16 `json:"abilities"` // Current wheel abilities
|
||||
Countered [6]int8 `json:"countered"` // Completion status
|
||||
Complete int8 `json:"complete"` // Overall completion (0/1)
|
||||
State int8 `json:"state"` // Current HO state
|
||||
CanShift bool `json:"can_shift"` // Whether shift is available
|
||||
ShiftIcon int16 `json:"shift_icon"` // Icon for shift ability
|
||||
SpellName string `json:"spell_name"`
|
||||
SpellDescription string `json:"spell_description"`
|
||||
TimeRemaining int32 `json:"time_remaining"` // milliseconds
|
||||
TotalTime int32 `json:"total_time"` // milliseconds
|
||||
Abilities [6]int16 `json:"abilities"` // Current wheel abilities
|
||||
Countered [6]int8 `json:"countered"` // Completion status
|
||||
Complete int8 `json:"complete"` // Overall completion (0/1)
|
||||
State int8 `json:"state"` // Current HO state
|
||||
CanShift bool `json:"can_shift"` // Whether shift is available
|
||||
ShiftIcon int16 `json:"shift_icon"` // Icon for shift ability
|
||||
}
|
||||
|
||||
// PlayerHOInfo contains player-specific HO information
|
||||
type PlayerHOInfo struct {
|
||||
CharacterID int32 `json:"character_id"`
|
||||
ParticipatingHOs []int64 `json:"participating_hos"` // HO instance IDs
|
||||
LastActivity time.Time `json:"last_activity"`
|
||||
TotalHOsJoined int64 `json:"total_hos_joined"`
|
||||
TotalHOsCompleted int64 `json:"total_hos_completed"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
CharacterID int32 `json:"character_id"`
|
||||
ParticipatingHOs []int64 `json:"participating_hos"` // HO instance IDs
|
||||
LastActivity time.Time `json:"last_activity"`
|
||||
TotalHOsJoined int64 `json:"total_hos_joined"`
|
||||
TotalHOsCompleted int64 `json:"total_hos_completed"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
}
|
||||
|
||||
// Configuration structure for HO system
|
||||
type HeroicOPConfig struct {
|
||||
DefaultWheelTimer int32 `json:"default_wheel_timer"` // milliseconds
|
||||
StarterChainTimeout int32 `json:"starter_chain_timeout"` // milliseconds
|
||||
MaxConcurrentHOs int `json:"max_concurrent_hos"`
|
||||
EnableLogging bool `json:"enable_logging"`
|
||||
EnableStatistics bool `json:"enable_statistics"`
|
||||
EnableShifting bool `json:"enable_shifting"`
|
||||
RequireClassMatch bool `json:"require_class_match"` // Enforce starter class restrictions
|
||||
AutoCleanupInterval int32 `json:"auto_cleanup_interval"` // seconds
|
||||
MaxHistoryEntries int `json:"max_history_entries"`
|
||||
DefaultWheelTimer int32 `json:"default_wheel_timer"` // milliseconds
|
||||
StarterChainTimeout int32 `json:"starter_chain_timeout"` // milliseconds
|
||||
MaxConcurrentHOs int `json:"max_concurrent_hos"`
|
||||
EnableLogging bool `json:"enable_logging"`
|
||||
EnableStatistics bool `json:"enable_statistics"`
|
||||
EnableShifting bool `json:"enable_shifting"`
|
||||
RequireClassMatch bool `json:"require_class_match"` // Enforce starter class restrictions
|
||||
AutoCleanupInterval int32 `json:"auto_cleanup_interval"` // seconds
|
||||
MaxHistoryEntries int `json:"max_history_entries"`
|
||||
}
|
@ -16,40 +16,40 @@ const (
|
||||
AlignmentNeutral = 3
|
||||
|
||||
// Transaction types for house history
|
||||
TransactionPurchase = 1
|
||||
TransactionUpkeep = 2
|
||||
TransactionDeposit = 3
|
||||
TransactionWithdrawal = 4
|
||||
TransactionAmenity = 5
|
||||
TransactionPurchase = 1
|
||||
TransactionUpkeep = 2
|
||||
TransactionDeposit = 3
|
||||
TransactionWithdrawal = 4
|
||||
TransactionAmenity = 5
|
||||
TransactionVaultExpansion = 6
|
||||
TransactionRent = 7
|
||||
TransactionForeclosure = 8
|
||||
TransactionTransfer = 9
|
||||
TransactionRepair = 10
|
||||
TransactionRent = 7
|
||||
TransactionForeclosure = 8
|
||||
TransactionTransfer = 9
|
||||
TransactionRepair = 10
|
||||
|
||||
// History position flags
|
||||
HistoryFlagPositive = 1
|
||||
HistoryFlagNegative = 0
|
||||
|
||||
// Upkeep periods (in seconds)
|
||||
UpkeepPeriodWeekly = 604800 // 7 days
|
||||
UpkeepPeriodMonthly = 2592000 // 30 days
|
||||
UpkeepGracePeriod = 259200 // 3 days
|
||||
UpkeepPeriodWeekly = 604800 // 7 days
|
||||
UpkeepPeriodMonthly = 2592000 // 30 days
|
||||
UpkeepGracePeriod = 259200 // 3 days
|
||||
|
||||
// House status flags
|
||||
HouseStatusActive = 0
|
||||
HouseStatusUpkeepDue = 1
|
||||
HouseStatusForeclosed = 2
|
||||
HouseStatusAbandoned = 3
|
||||
HouseStatusActive = 0
|
||||
HouseStatusUpkeepDue = 1
|
||||
HouseStatusForeclosed = 2
|
||||
HouseStatusAbandoned = 3
|
||||
|
||||
// Maximum values
|
||||
MaxHouseName = 64
|
||||
MaxReasonLength = 255
|
||||
MaxDepositHistory = 100
|
||||
MaxHouseName = 64
|
||||
MaxReasonLength = 255
|
||||
MaxDepositHistory = 100
|
||||
MaxTransactionHistory = 500
|
||||
MaxVaultSlots = 200
|
||||
MaxAmenities = 50
|
||||
MaxAccessEntries = 100
|
||||
MaxVaultSlots = 200
|
||||
MaxAmenities = 50
|
||||
MaxAccessEntries = 100
|
||||
|
||||
// Database retry settings
|
||||
MaxDatabaseRetries = 3
|
||||
@ -60,25 +60,25 @@ const (
|
||||
MaxEscrowStatus = 10000000 // 10 million status
|
||||
|
||||
// Visit permissions
|
||||
VisitPermissionPublic = 0
|
||||
VisitPermissionFriends = 1
|
||||
VisitPermissionGuild = 2
|
||||
VisitPermissionPublic = 0
|
||||
VisitPermissionFriends = 1
|
||||
VisitPermissionGuild = 2
|
||||
VisitPermissionInviteOnly = 3
|
||||
VisitPermissionPrivate = 4
|
||||
VisitPermissionPrivate = 4
|
||||
|
||||
// Housing opcodes/packet types
|
||||
OpHousePurchase = "PlayerHousePurchase"
|
||||
OpHousingList = "CharacterHousingList"
|
||||
OpBaseHouseWindow = "PlayerHouseBaseScreen"
|
||||
OpHouseVisitWindow = "PlayerHouseVisit"
|
||||
OpBuyHouse = "BuyHouse"
|
||||
OpEnterHouse = "EnterHouse"
|
||||
OpUpdateHouseAccess = "UpdateHouseAccessDataMsg"
|
||||
OpHouseDeposit = "HouseDeposit"
|
||||
OpHouseWithdrawal = "HouseWithdrawal"
|
||||
OpPlaceItem = "PlaceItemInHouse"
|
||||
OpRemoveItem = "RemoveItemFromHouse"
|
||||
OpUpdateAmenities = "UpdateHouseAmenities"
|
||||
OpHousePurchase = "PlayerHousePurchase"
|
||||
OpHousingList = "CharacterHousingList"
|
||||
OpBaseHouseWindow = "PlayerHouseBaseScreen"
|
||||
OpHouseVisitWindow = "PlayerHouseVisit"
|
||||
OpBuyHouse = "BuyHouse"
|
||||
OpEnterHouse = "EnterHouse"
|
||||
OpUpdateHouseAccess = "UpdateHouseAccessDataMsg"
|
||||
OpHouseDeposit = "HouseDeposit"
|
||||
OpHouseWithdrawal = "HouseWithdrawal"
|
||||
OpPlaceItem = "PlaceItemInHouse"
|
||||
OpRemoveItem = "RemoveItemFromHouse"
|
||||
OpUpdateAmenities = "UpdateHouseAmenities"
|
||||
|
||||
// Error messages
|
||||
ErrHouseNotFound = "house not found"
|
||||
@ -95,17 +95,17 @@ const (
|
||||
ErrMaxHousesReached = "maximum number of houses reached"
|
||||
|
||||
// Default upkeep costs (can be overridden per house type)
|
||||
DefaultUpkeepCoins = 10000 // 1 gold
|
||||
DefaultUpkeepCoins = 10000 // 1 gold
|
||||
DefaultUpkeepStatus = 100
|
||||
|
||||
// Item placement constants
|
||||
MaxItemsPerHouse = 1000
|
||||
MaxItemStackSize = 100
|
||||
ItemPlacementRadius = 50.0 // Maximum distance from spawn point
|
||||
MaxItemsPerHouse = 1000
|
||||
MaxItemStackSize = 100
|
||||
ItemPlacementRadius = 50.0 // Maximum distance from spawn point
|
||||
|
||||
// House zones configuration
|
||||
DefaultInstanceLifetime = 3600 // 1 hour in seconds
|
||||
MaxHouseVisitors = 50 // Maximum concurrent visitors
|
||||
MaxHouseVisitors = 50 // Maximum concurrent visitors
|
||||
|
||||
// Amenity types
|
||||
AmenityVaultExpansion = 1
|
||||
@ -118,8 +118,8 @@ const (
|
||||
AmenityTeleporter = 8
|
||||
|
||||
// Foreclosure settings
|
||||
ForeclosureWarningDays = 7 // Days before foreclosure
|
||||
ForeclosureNoticeDays = 3 // Days of final notice
|
||||
ForeclosureWarningDays = 7 // Days before foreclosure
|
||||
ForeclosureNoticeDays = 3 // Days of final notice
|
||||
|
||||
// Deposit limits
|
||||
MinDepositAmount = 1
|
||||
@ -132,28 +132,28 @@ const (
|
||||
|
||||
// House type constants for common house types
|
||||
const (
|
||||
HouseTypeInn = 1
|
||||
HouseTypeCottage = 2
|
||||
HouseTypeApartment = 3
|
||||
HouseTypeHouse = 4
|
||||
HouseTypeMansion = 5
|
||||
HouseTypeKeep = 6
|
||||
HouseTypeGuildHall = 7
|
||||
HouseTypeInn = 1
|
||||
HouseTypeCottage = 2
|
||||
HouseTypeApartment = 3
|
||||
HouseTypeHouse = 4
|
||||
HouseTypeMansion = 5
|
||||
HouseTypeKeep = 6
|
||||
HouseTypeGuildHall = 7
|
||||
HouseTypePrestigeHome = 8
|
||||
)
|
||||
|
||||
// Access permission flags (bitwise)
|
||||
const (
|
||||
PermissionEnter = 1 << iota // Can enter the house
|
||||
PermissionPlace = 1 << iota // Can place items
|
||||
PermissionRemove = 1 << iota // Can remove items
|
||||
PermissionMove = 1 << iota // Can move items
|
||||
PermissionVault = 1 << iota // Can access vault
|
||||
PermissionDeposit = 1 << iota // Can make deposits
|
||||
PermissionWithdraw = 1 << iota // Can make withdrawals
|
||||
PermissionInvite = 1 << iota // Can invite others
|
||||
PermissionKick = 1 << iota // Can kick visitors
|
||||
PermissionAdmin = 1 << iota // Full administrative access
|
||||
PermissionEnter = 1 << iota // Can enter the house
|
||||
PermissionPlace = 1 << iota // Can place items
|
||||
PermissionRemove = 1 << iota // Can remove items
|
||||
PermissionMove = 1 << iota // Can move items
|
||||
PermissionVault = 1 << iota // Can access vault
|
||||
PermissionDeposit = 1 << iota // Can make deposits
|
||||
PermissionWithdraw = 1 << iota // Can make withdrawals
|
||||
PermissionInvite = 1 << iota // Can invite others
|
||||
PermissionKick = 1 << iota // Can kick visitors
|
||||
PermissionAdmin = 1 << iota // Full administrative access
|
||||
)
|
||||
|
||||
// Default permission sets
|
||||
@ -220,13 +220,13 @@ var HouseTypeNames = map[int]string{
|
||||
|
||||
// Default costs for house types (in copper coins)
|
||||
var DefaultHouseCosts = map[int]int64{
|
||||
HouseTypeInn: 50000, // 5 gold
|
||||
HouseTypeCottage: 200000, // 20 gold
|
||||
HouseTypeApartment: 500000, // 50 gold
|
||||
HouseTypeHouse: 1000000, // 100 gold
|
||||
HouseTypeMansion: 5000000, // 500 gold
|
||||
HouseTypeKeep: 10000000, // 1000 gold
|
||||
HouseTypeGuildHall: 50000000, // 5000 gold
|
||||
HouseTypeInn: 50000, // 5 gold
|
||||
HouseTypeCottage: 200000, // 20 gold
|
||||
HouseTypeApartment: 500000, // 50 gold
|
||||
HouseTypeHouse: 1000000, // 100 gold
|
||||
HouseTypeMansion: 5000000, // 500 gold
|
||||
HouseTypeKeep: 10000000, // 1000 gold
|
||||
HouseTypeGuildHall: 50000000, // 5000 gold
|
||||
HouseTypePrestigeHome: 100000000, // 10000 gold
|
||||
}
|
||||
|
||||
@ -244,13 +244,13 @@ var DefaultHouseStatusCosts = map[int]int64{
|
||||
|
||||
// Default upkeep costs (in copper coins per week)
|
||||
var DefaultHouseUpkeepCosts = map[int]int64{
|
||||
HouseTypeInn: 5000, // 50 silver
|
||||
HouseTypeCottage: 10000, // 1 gold
|
||||
HouseTypeApartment: 25000, // 2.5 gold
|
||||
HouseTypeHouse: 50000, // 5 gold
|
||||
HouseTypeMansion: 100000, // 10 gold
|
||||
HouseTypeKeep: 200000, // 20 gold
|
||||
HouseTypeGuildHall: 500000, // 50 gold
|
||||
HouseTypeInn: 5000, // 50 silver
|
||||
HouseTypeCottage: 10000, // 1 gold
|
||||
HouseTypeApartment: 25000, // 2.5 gold
|
||||
HouseTypeHouse: 50000, // 5 gold
|
||||
HouseTypeMansion: 100000, // 10 gold
|
||||
HouseTypeKeep: 200000, // 20 gold
|
||||
HouseTypeGuildHall: 500000, // 50 gold
|
||||
HouseTypePrestigeHome: 1000000, // 100 gold
|
||||
}
|
||||
|
||||
|
@ -985,8 +985,8 @@ func (dhm *DatabaseHousingManager) GetHouseStatistics(ctx context.Context) (*Hou
|
||||
// Get basic counts
|
||||
queries := map[string]*int64{
|
||||
"SELECT COUNT(*) FROM character_houses": &stats.TotalHouses,
|
||||
"SELECT COUNT(*) FROM character_houses WHERE status = 0": &stats.ActiveHouses,
|
||||
"SELECT COUNT(*) FROM character_houses WHERE status = 2": &stats.ForelosedHouses,
|
||||
"SELECT COUNT(*) FROM character_houses WHERE status = 0": &stats.ActiveHouses,
|
||||
"SELECT COUNT(*) FROM character_houses WHERE status = 2": &stats.ForelosedHouses,
|
||||
"SELECT COUNT(*) FROM character_house_deposits": &stats.TotalDeposits,
|
||||
"SELECT COUNT(*) FROM character_house_history WHERE pos_flag = 0": &stats.TotalWithdrawals,
|
||||
}
|
||||
|
@ -210,14 +210,14 @@ type ZoneInfo struct {
|
||||
|
||||
// HouseInstance contains active house instance information
|
||||
type HouseInstance struct {
|
||||
InstanceID int32 `json:"instance_id"`
|
||||
HouseID int64 `json:"house_id"`
|
||||
OwnerID int32 `json:"owner_id"`
|
||||
ZoneID int32 `json:"zone_id"`
|
||||
CreatedTime time.Time `json:"created_time"`
|
||||
LastActivity time.Time `json:"last_activity"`
|
||||
CurrentVisitors []int32 `json:"current_visitors"`
|
||||
IsActive bool `json:"is_active"`
|
||||
InstanceID int32 `json:"instance_id"`
|
||||
HouseID int64 `json:"house_id"`
|
||||
OwnerID int32 `json:"owner_id"`
|
||||
ZoneID int32 `json:"zone_id"`
|
||||
CreatedTime time.Time `json:"created_time"`
|
||||
LastActivity time.Time `json:"last_activity"`
|
||||
CurrentVisitors []int32 `json:"current_visitors"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
// Adapter interfaces for integration with existing systems
|
||||
|
@ -7,22 +7,22 @@ import (
|
||||
|
||||
// HouseZone represents a house type that can be purchased
|
||||
type HouseZone struct {
|
||||
mu sync.RWMutex
|
||||
ID int32 `json:"id"` // Unique house type identifier
|
||||
Name string `json:"name"` // House name/type
|
||||
ZoneID int32 `json:"zone_id"` // Zone where house is located
|
||||
CostCoin int64 `json:"cost_coin"` // Purchase cost in coins
|
||||
CostStatus int64 `json:"cost_status"` // Purchase cost in status points
|
||||
UpkeepCoin int64 `json:"upkeep_coin"` // Upkeep cost in coins
|
||||
UpkeepStatus int64 `json:"upkeep_status"` // Upkeep cost in status points
|
||||
Alignment int8 `json:"alignment"` // Alignment requirement
|
||||
GuildLevel int8 `json:"guild_level"` // Required guild level
|
||||
VaultSlots int `json:"vault_slots"` // Number of vault storage slots
|
||||
MaxItems int `json:"max_items"` // Maximum items that can be placed
|
||||
MaxVisitors int `json:"max_visitors"` // Maximum concurrent visitors
|
||||
UpkeepPeriod int32 `json:"upkeep_period"` // Upkeep period in seconds
|
||||
Description string `json:"description"` // Description text
|
||||
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
|
||||
mu sync.RWMutex
|
||||
ID int32 `json:"id"` // Unique house type identifier
|
||||
Name string `json:"name"` // House name/type
|
||||
ZoneID int32 `json:"zone_id"` // Zone where house is located
|
||||
CostCoin int64 `json:"cost_coin"` // Purchase cost in coins
|
||||
CostStatus int64 `json:"cost_status"` // Purchase cost in status points
|
||||
UpkeepCoin int64 `json:"upkeep_coin"` // Upkeep cost in coins
|
||||
UpkeepStatus int64 `json:"upkeep_status"` // Upkeep cost in status points
|
||||
Alignment int8 `json:"alignment"` // Alignment requirement
|
||||
GuildLevel int8 `json:"guild_level"` // Required guild level
|
||||
VaultSlots int `json:"vault_slots"` // Number of vault storage slots
|
||||
MaxItems int `json:"max_items"` // Maximum items that can be placed
|
||||
MaxVisitors int `json:"max_visitors"` // Maximum concurrent visitors
|
||||
UpkeepPeriod int32 `json:"upkeep_period"` // Upkeep period in seconds
|
||||
Description string `json:"description"` // Description text
|
||||
SaveNeeded bool `json:"-"` // Flag indicating if database save is needed
|
||||
}
|
||||
|
||||
// PlayerHouse represents a house owned by a player
|
||||
@ -48,13 +48,13 @@ type PlayerHouse struct {
|
||||
|
||||
// HouseDeposit represents a deposit transaction
|
||||
type HouseDeposit struct {
|
||||
Timestamp time.Time `json:"timestamp"` // When deposit was made
|
||||
Amount int64 `json:"amount"` // Coin amount deposited
|
||||
LastAmount int64 `json:"last_amount"` // Previous coin amount
|
||||
Status int64 `json:"status"` // Status points deposited
|
||||
LastStatus int64 `json:"last_status"` // Previous status points
|
||||
Name string `json:"name"` // Player who made deposit
|
||||
CharacterID int32 `json:"character_id"` // Character ID who made deposit
|
||||
Timestamp time.Time `json:"timestamp"` // When deposit was made
|
||||
Amount int64 `json:"amount"` // Coin amount deposited
|
||||
LastAmount int64 `json:"last_amount"` // Previous coin amount
|
||||
Status int64 `json:"status"` // Status points deposited
|
||||
LastStatus int64 `json:"last_status"` // Previous status points
|
||||
Name string `json:"name"` // Player who made deposit
|
||||
CharacterID int32 `json:"character_id"` // Character ID who made deposit
|
||||
}
|
||||
|
||||
// HouseHistory represents a house transaction history entry
|
||||
@ -71,99 +71,99 @@ type HouseHistory struct {
|
||||
|
||||
// HouseAccess represents access permissions for a player
|
||||
type HouseAccess struct {
|
||||
CharacterID int32 `json:"character_id"` // Character being granted access
|
||||
PlayerName string `json:"player_name"` // Player name
|
||||
AccessLevel int8 `json:"access_level"` // Access level
|
||||
Permissions int32 `json:"permissions"` // Permission flags
|
||||
GrantedBy int32 `json:"granted_by"` // Who granted the access
|
||||
GrantedDate time.Time `json:"granted_date"` // When access was granted
|
||||
ExpiresDate time.Time `json:"expires_date"` // When access expires (0 = never)
|
||||
Notes string `json:"notes"` // Optional notes
|
||||
CharacterID int32 `json:"character_id"` // Character being granted access
|
||||
PlayerName string `json:"player_name"` // Player name
|
||||
AccessLevel int8 `json:"access_level"` // Access level
|
||||
Permissions int32 `json:"permissions"` // Permission flags
|
||||
GrantedBy int32 `json:"granted_by"` // Who granted the access
|
||||
GrantedDate time.Time `json:"granted_date"` // When access was granted
|
||||
ExpiresDate time.Time `json:"expires_date"` // When access expires (0 = never)
|
||||
Notes string `json:"notes"` // Optional notes
|
||||
}
|
||||
|
||||
// HouseAmenity represents a purchased house amenity
|
||||
type HouseAmenity struct {
|
||||
ID int32 `json:"id"` // Amenity ID
|
||||
Type int `json:"type"` // Amenity type
|
||||
Name string `json:"name"` // Amenity name
|
||||
Cost int64 `json:"cost"` // Purchase cost
|
||||
StatusCost int64 `json:"status_cost"` // Status cost
|
||||
PurchaseDate time.Time `json:"purchase_date"` // When purchased
|
||||
X float32 `json:"x"` // X position
|
||||
Y float32 `json:"y"` // Y position
|
||||
Z float32 `json:"z"` // Z position
|
||||
Heading float32 `json:"heading"` // Heading
|
||||
IsActive bool `json:"is_active"` // Whether amenity is active
|
||||
ID int32 `json:"id"` // Amenity ID
|
||||
Type int `json:"type"` // Amenity type
|
||||
Name string `json:"name"` // Amenity name
|
||||
Cost int64 `json:"cost"` // Purchase cost
|
||||
StatusCost int64 `json:"status_cost"` // Status cost
|
||||
PurchaseDate time.Time `json:"purchase_date"` // When purchased
|
||||
X float32 `json:"x"` // X position
|
||||
Y float32 `json:"y"` // Y position
|
||||
Z float32 `json:"z"` // Z position
|
||||
Heading float32 `json:"heading"` // Heading
|
||||
IsActive bool `json:"is_active"` // Whether amenity is active
|
||||
}
|
||||
|
||||
// HouseItem represents an item placed in a house
|
||||
type HouseItem struct {
|
||||
ID int64 `json:"id"` // Item unique ID
|
||||
ItemID int32 `json:"item_id"` // Item template ID
|
||||
CharacterID int32 `json:"character_id"` // Who placed the item
|
||||
X float32 `json:"x"` // X position
|
||||
Y float32 `json:"y"` // Y position
|
||||
Z float32 `json:"z"` // Z position
|
||||
Heading float32 `json:"heading"` // Heading
|
||||
PitchX float32 `json:"pitch_x"` // Pitch X
|
||||
PitchY float32 `json:"pitch_y"` // Pitch Y
|
||||
RollX float32 `json:"roll_x"` // Roll X
|
||||
RollY float32 `json:"roll_y"` // Roll Y
|
||||
PlacedDate time.Time `json:"placed_date"` // When item was placed
|
||||
Quantity int32 `json:"quantity"` // Item quantity
|
||||
Condition int8 `json:"condition"` // Item condition
|
||||
House string `json:"house"` // House identifier
|
||||
ID int64 `json:"id"` // Item unique ID
|
||||
ItemID int32 `json:"item_id"` // Item template ID
|
||||
CharacterID int32 `json:"character_id"` // Who placed the item
|
||||
X float32 `json:"x"` // X position
|
||||
Y float32 `json:"y"` // Y position
|
||||
Z float32 `json:"z"` // Z position
|
||||
Heading float32 `json:"heading"` // Heading
|
||||
PitchX float32 `json:"pitch_x"` // Pitch X
|
||||
PitchY float32 `json:"pitch_y"` // Pitch Y
|
||||
RollX float32 `json:"roll_x"` // Roll X
|
||||
RollY float32 `json:"roll_y"` // Roll Y
|
||||
PlacedDate time.Time `json:"placed_date"` // When item was placed
|
||||
Quantity int32 `json:"quantity"` // Item quantity
|
||||
Condition int8 `json:"condition"` // Item condition
|
||||
House string `json:"house"` // House identifier
|
||||
}
|
||||
|
||||
// HouseSettings represents house configuration settings
|
||||
type HouseSettings struct {
|
||||
HouseName string `json:"house_name"` // Custom house name
|
||||
VisitPermission int8 `json:"visit_permission"` // Who can visit
|
||||
PublicNote string `json:"public_note"` // Public note displayed
|
||||
PrivateNote string `json:"private_note"` // Private note for owner
|
||||
AllowFriends bool `json:"allow_friends"` // Allow friends to visit
|
||||
AllowGuild bool `json:"allow_guild"` // Allow guild members to visit
|
||||
RequireApproval bool `json:"require_approval"` // Require approval for visits
|
||||
ShowOnDirectory bool `json:"show_on_directory"` // Show in house directory
|
||||
AllowDecoration bool `json:"allow_decoration"` // Allow others to decorate
|
||||
TaxExempt bool `json:"tax_exempt"` // Tax exemption status
|
||||
HouseName string `json:"house_name"` // Custom house name
|
||||
VisitPermission int8 `json:"visit_permission"` // Who can visit
|
||||
PublicNote string `json:"public_note"` // Public note displayed
|
||||
PrivateNote string `json:"private_note"` // Private note for owner
|
||||
AllowFriends bool `json:"allow_friends"` // Allow friends to visit
|
||||
AllowGuild bool `json:"allow_guild"` // Allow guild members to visit
|
||||
RequireApproval bool `json:"require_approval"` // Require approval for visits
|
||||
ShowOnDirectory bool `json:"show_on_directory"` // Show in house directory
|
||||
AllowDecoration bool `json:"allow_decoration"` // Allow others to decorate
|
||||
TaxExempt bool `json:"tax_exempt"` // Tax exemption status
|
||||
}
|
||||
|
||||
// HousingManager manages the overall housing system
|
||||
type HousingManager struct {
|
||||
mu sync.RWMutex
|
||||
houseZones map[int32]*HouseZone // Available house types
|
||||
playerHouses map[int64]*PlayerHouse // All player houses by unique ID
|
||||
characterHouses map[int32][]*PlayerHouse // Houses by character ID
|
||||
zoneInstances map[int32]map[int32]*PlayerHouse // Houses by zone and instance
|
||||
database HousingDatabase
|
||||
clientManager ClientManager
|
||||
playerManager PlayerManager
|
||||
itemManager ItemManager
|
||||
zoneManager ZoneManager
|
||||
eventHandler HousingEventHandler
|
||||
logger LogHandler
|
||||
mu sync.RWMutex
|
||||
houseZones map[int32]*HouseZone // Available house types
|
||||
playerHouses map[int64]*PlayerHouse // All player houses by unique ID
|
||||
characterHouses map[int32][]*PlayerHouse // Houses by character ID
|
||||
zoneInstances map[int32]map[int32]*PlayerHouse // Houses by zone and instance
|
||||
database HousingDatabase
|
||||
clientManager ClientManager
|
||||
playerManager PlayerManager
|
||||
itemManager ItemManager
|
||||
zoneManager ZoneManager
|
||||
eventHandler HousingEventHandler
|
||||
logger LogHandler
|
||||
// Configuration
|
||||
enableUpkeep bool
|
||||
enableForeclosure bool
|
||||
upkeepGracePeriod int32
|
||||
enableUpkeep bool
|
||||
enableForeclosure bool
|
||||
upkeepGracePeriod int32
|
||||
maxHousesPerPlayer int
|
||||
enableStatistics bool
|
||||
enableStatistics bool
|
||||
}
|
||||
|
||||
// HousingStatistics tracks housing system usage
|
||||
type HousingStatistics struct {
|
||||
TotalHouses int64 `json:"total_houses"`
|
||||
ActiveHouses int64 `json:"active_houses"`
|
||||
ForelosedHouses int64 `json:"foreclosed_houses"`
|
||||
TotalDeposits int64 `json:"total_deposits"`
|
||||
TotalWithdrawals int64 `json:"total_withdrawals"`
|
||||
AverageUpkeepPaid float64 `json:"average_upkeep_paid"`
|
||||
MostPopularHouseType int32 `json:"most_popular_house_type"`
|
||||
HousesByType map[int32]int64 `json:"houses_by_type"`
|
||||
HousesByAlignment map[int8]int64 `json:"houses_by_alignment"`
|
||||
RevenueByType map[int]int64 `json:"revenue_by_type"`
|
||||
TopDepositors []PlayerDeposits `json:"top_depositors"`
|
||||
TotalHouses int64 `json:"total_houses"`
|
||||
ActiveHouses int64 `json:"active_houses"`
|
||||
ForelosedHouses int64 `json:"foreclosed_houses"`
|
||||
TotalDeposits int64 `json:"total_deposits"`
|
||||
TotalWithdrawals int64 `json:"total_withdrawals"`
|
||||
AverageUpkeepPaid float64 `json:"average_upkeep_paid"`
|
||||
MostPopularHouseType int32 `json:"most_popular_house_type"`
|
||||
HousesByType map[int32]int64 `json:"houses_by_type"`
|
||||
HousesByAlignment map[int8]int64 `json:"houses_by_alignment"`
|
||||
RevenueByType map[int]int64 `json:"revenue_by_type"`
|
||||
TopDepositors []PlayerDeposits `json:"top_depositors"`
|
||||
}
|
||||
|
||||
// PlayerDeposits tracks deposits by player
|
||||
@ -176,17 +176,17 @@ type PlayerDeposits struct {
|
||||
|
||||
// HousingSearchCriteria for searching houses
|
||||
type HousingSearchCriteria struct {
|
||||
OwnerName string `json:"owner_name"` // Filter by owner name
|
||||
HouseType int32 `json:"house_type"` // Filter by house type
|
||||
Alignment int8 `json:"alignment"` // Filter by alignment
|
||||
MinCost int64 `json:"min_cost"` // Minimum cost filter
|
||||
MaxCost int64 `json:"max_cost"` // Maximum cost filter
|
||||
Zone int32 `json:"zone"` // Filter by zone
|
||||
VisitableOnly bool `json:"visitable_only"` // Only houses that can be visited
|
||||
PublicOnly bool `json:"public_only"` // Only publicly accessible houses
|
||||
NamePattern string `json:"name_pattern"` // Filter by house name pattern
|
||||
HasAmenities bool `json:"has_amenities"` // Filter houses with amenities
|
||||
MinVaultSlots int `json:"min_vault_slots"` // Minimum vault slots
|
||||
OwnerName string `json:"owner_name"` // Filter by owner name
|
||||
HouseType int32 `json:"house_type"` // Filter by house type
|
||||
Alignment int8 `json:"alignment"` // Filter by alignment
|
||||
MinCost int64 `json:"min_cost"` // Minimum cost filter
|
||||
MaxCost int64 `json:"max_cost"` // Maximum cost filter
|
||||
Zone int32 `json:"zone"` // Filter by zone
|
||||
VisitableOnly bool `json:"visitable_only"` // Only houses that can be visited
|
||||
PublicOnly bool `json:"public_only"` // Only publicly accessible houses
|
||||
NamePattern string `json:"name_pattern"` // Filter by house name pattern
|
||||
HasAmenities bool `json:"has_amenities"` // Filter houses with amenities
|
||||
MinVaultSlots int `json:"min_vault_slots"` // Minimum vault slots
|
||||
}
|
||||
|
||||
// Database record types for data persistence
|
||||
@ -211,36 +211,36 @@ type HouseZoneData struct {
|
||||
|
||||
// PlayerHouseData represents database record for player houses
|
||||
type PlayerHouseData struct {
|
||||
UniqueID int64 `json:"unique_id"`
|
||||
CharacterID int32 `json:"char_id"`
|
||||
HouseID int32 `json:"house_id"`
|
||||
InstanceID int32 `json:"instance_id"`
|
||||
UpkeepDue time.Time `json:"upkeep_due"`
|
||||
EscrowCoins int64 `json:"escrow_coins"`
|
||||
EscrowStatus int64 `json:"escrow_status"`
|
||||
Status int8 `json:"status"`
|
||||
HouseName string `json:"house_name"`
|
||||
VisitPermission int8 `json:"visit_permission"`
|
||||
PublicNote string `json:"public_note"`
|
||||
PrivateNote string `json:"private_note"`
|
||||
AllowFriends bool `json:"allow_friends"`
|
||||
AllowGuild bool `json:"allow_guild"`
|
||||
RequireApproval bool `json:"require_approval"`
|
||||
ShowOnDirectory bool `json:"show_on_directory"`
|
||||
AllowDecoration bool `json:"allow_decoration"`
|
||||
TaxExempt bool `json:"tax_exempt"`
|
||||
UniqueID int64 `json:"unique_id"`
|
||||
CharacterID int32 `json:"char_id"`
|
||||
HouseID int32 `json:"house_id"`
|
||||
InstanceID int32 `json:"instance_id"`
|
||||
UpkeepDue time.Time `json:"upkeep_due"`
|
||||
EscrowCoins int64 `json:"escrow_coins"`
|
||||
EscrowStatus int64 `json:"escrow_status"`
|
||||
Status int8 `json:"status"`
|
||||
HouseName string `json:"house_name"`
|
||||
VisitPermission int8 `json:"visit_permission"`
|
||||
PublicNote string `json:"public_note"`
|
||||
PrivateNote string `json:"private_note"`
|
||||
AllowFriends bool `json:"allow_friends"`
|
||||
AllowGuild bool `json:"allow_guild"`
|
||||
RequireApproval bool `json:"require_approval"`
|
||||
ShowOnDirectory bool `json:"show_on_directory"`
|
||||
AllowDecoration bool `json:"allow_decoration"`
|
||||
TaxExempt bool `json:"tax_exempt"`
|
||||
}
|
||||
|
||||
// HouseDepositData represents database record for deposits
|
||||
type HouseDepositData struct {
|
||||
HouseID int64 `json:"house_id"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Amount int64 `json:"amount"`
|
||||
LastAmount int64 `json:"last_amount"`
|
||||
Status int64 `json:"status"`
|
||||
LastStatus int64 `json:"last_status"`
|
||||
Name string `json:"name"`
|
||||
CharacterID int32 `json:"character_id"`
|
||||
HouseID int64 `json:"house_id"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Amount int64 `json:"amount"`
|
||||
LastAmount int64 `json:"last_amount"`
|
||||
Status int64 `json:"status"`
|
||||
LastStatus int64 `json:"last_status"`
|
||||
Name string `json:"name"`
|
||||
CharacterID int32 `json:"character_id"`
|
||||
}
|
||||
|
||||
// HouseHistoryData represents database record for house history
|
||||
@ -258,15 +258,15 @@ type HouseHistoryData struct {
|
||||
|
||||
// HouseAccessData represents database record for house access
|
||||
type HouseAccessData struct {
|
||||
HouseID int64 `json:"house_id"`
|
||||
CharacterID int32 `json:"character_id"`
|
||||
PlayerName string `json:"player_name"`
|
||||
AccessLevel int8 `json:"access_level"`
|
||||
Permissions int32 `json:"permissions"`
|
||||
GrantedBy int32 `json:"granted_by"`
|
||||
GrantedDate time.Time `json:"granted_date"`
|
||||
ExpiresDate time.Time `json:"expires_date"`
|
||||
Notes string `json:"notes"`
|
||||
HouseID int64 `json:"house_id"`
|
||||
CharacterID int32 `json:"character_id"`
|
||||
PlayerName string `json:"player_name"`
|
||||
AccessLevel int8 `json:"access_level"`
|
||||
Permissions int32 `json:"permissions"`
|
||||
GrantedBy int32 `json:"granted_by"`
|
||||
GrantedDate time.Time `json:"granted_date"`
|
||||
ExpiresDate time.Time `json:"expires_date"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
// HouseAmenityData represents database record for house amenities
|
||||
@ -328,24 +328,24 @@ type HouseListPacketData struct {
|
||||
|
||||
// PlayerHouseInfo represents house info for list display
|
||||
type PlayerHouseInfo struct {
|
||||
UniqueID int64 `json:"unique_id"`
|
||||
Name string `json:"name"`
|
||||
HouseType string `json:"house_type"`
|
||||
UniqueID int64 `json:"unique_id"`
|
||||
Name string `json:"name"`
|
||||
HouseType string `json:"house_type"`
|
||||
UpkeepDue time.Time `json:"upkeep_due"`
|
||||
EscrowCoins int64 `json:"escrow_coins"`
|
||||
EscrowStatus int64 `json:"escrow_status"`
|
||||
Status int8 `json:"status"`
|
||||
CanEnter bool `json:"can_enter"`
|
||||
EscrowCoins int64 `json:"escrow_coins"`
|
||||
EscrowStatus int64 `json:"escrow_status"`
|
||||
Status int8 `json:"status"`
|
||||
CanEnter bool `json:"can_enter"`
|
||||
}
|
||||
|
||||
// BaseHouseWindowPacketData represents data for main house management UI
|
||||
type BaseHouseWindowPacketData struct {
|
||||
HouseInfo PlayerHouseInfo `json:"house_info"`
|
||||
HouseInfo PlayerHouseInfo `json:"house_info"`
|
||||
RecentDeposits []HouseDeposit `json:"recent_deposits"`
|
||||
RecentHistory []HouseHistory `json:"recent_history"`
|
||||
Amenities []HouseAmenity `json:"amenities"`
|
||||
Settings HouseSettings `json:"settings"`
|
||||
CanManage bool `json:"can_manage"`
|
||||
Amenities []HouseAmenity `json:"amenities"`
|
||||
Settings HouseSettings `json:"settings"`
|
||||
CanManage bool `json:"can_manage"`
|
||||
}
|
||||
|
||||
// HouseVisitPacketData represents data for house visit UI
|
||||
@ -355,13 +355,13 @@ type HouseVisitPacketData struct {
|
||||
|
||||
// VisitableHouse represents a house that can be visited
|
||||
type VisitableHouse struct {
|
||||
UniqueID int64 `json:"unique_id"`
|
||||
OwnerName string `json:"owner_name"`
|
||||
HouseName string `json:"house_name"`
|
||||
HouseType string `json:"house_type"`
|
||||
PublicNote string `json:"public_note"`
|
||||
CanVisit bool `json:"can_visit"`
|
||||
RequiresApproval bool `json:"requires_approval"`
|
||||
UniqueID int64 `json:"unique_id"`
|
||||
OwnerName string `json:"owner_name"`
|
||||
HouseName string `json:"house_name"`
|
||||
HouseType string `json:"house_type"`
|
||||
PublicNote string `json:"public_note"`
|
||||
CanVisit bool `json:"can_visit"`
|
||||
RequiresApproval bool `json:"requires_approval"`
|
||||
}
|
||||
|
||||
// Event structures for housing system events
|
||||
@ -378,13 +378,13 @@ type HousingEvent struct {
|
||||
|
||||
// Configuration structure for housing system
|
||||
type HousingConfig struct {
|
||||
EnableUpkeep bool `json:"enable_upkeep"`
|
||||
EnableForeclosure bool `json:"enable_foreclosure"`
|
||||
UpkeepGracePeriod int32 `json:"upkeep_grace_period"` // seconds
|
||||
MaxHousesPerPlayer int `json:"max_houses_per_player"`
|
||||
EnableStatistics bool `json:"enable_statistics"`
|
||||
AutoCleanupInterval int32 `json:"auto_cleanup_interval"` // seconds
|
||||
MaxHistoryEntries int `json:"max_history_entries"`
|
||||
MaxDepositEntries int `json:"max_deposit_entries"`
|
||||
EnableUpkeep bool `json:"enable_upkeep"`
|
||||
EnableForeclosure bool `json:"enable_foreclosure"`
|
||||
UpkeepGracePeriod int32 `json:"upkeep_grace_period"` // seconds
|
||||
MaxHousesPerPlayer int `json:"max_houses_per_player"`
|
||||
EnableStatistics bool `json:"enable_statistics"`
|
||||
AutoCleanupInterval int32 `json:"auto_cleanup_interval"` // seconds
|
||||
MaxHistoryEntries int `json:"max_history_entries"`
|
||||
MaxDepositEntries int `json:"max_deposit_entries"`
|
||||
DefaultInstanceLifetime int32 `json:"default_instance_lifetime"` // seconds
|
||||
}
|
686
internal/items/constants.go
Normal file
686
internal/items/constants.go
Normal 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
|
||||
)
|
555
internal/items/equipment_list.go
Normal file
555
internal/items/equipment_list.go
Normal 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")
|
||||
}
|
727
internal/items/interfaces.go
Normal file
727
internal/items/interfaces.go
Normal 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
1009
internal/items/item.go
Normal file
File diff suppressed because it is too large
Load Diff
829
internal/items/items_test.go
Normal file
829
internal/items/items_test.go
Normal 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()
|
||||
}
|
||||
}
|
688
internal/items/master_list.go
Normal file
688
internal/items/master_list.go
Normal 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")
|
||||
}
|
962
internal/items/player_list.go
Normal file
962
internal/items/player_list.go
Normal 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
561
internal/items/types.go
Normal 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")
|
||||
)
|
@ -6,18 +6,18 @@ const (
|
||||
MaxLanguageNameLength = 50
|
||||
|
||||
// Special language IDs (common in EQ2)
|
||||
LanguageIDCommon = 0 // Common tongue (default)
|
||||
LanguageIDElvish = 1 // Elvish
|
||||
LanguageIDDwarven = 2 // Dwarven
|
||||
LanguageIDHalfling = 3 // Halfling
|
||||
LanguageIDGnomish = 4 // Gnomish
|
||||
LanguageIDIksar = 5 // Iksar
|
||||
LanguageIDTrollish = 6 // Trollish
|
||||
LanguageIDOgrish = 7 // Ogrish
|
||||
LanguageIDFae = 8 // Fae
|
||||
LanguageIDArasai = 9 // Arasai
|
||||
LanguageIDSarnak = 10 // Sarnak
|
||||
LanguageIDFroglok = 11 // Froglok
|
||||
LanguageIDCommon = 0 // Common tongue (default)
|
||||
LanguageIDElvish = 1 // Elvish
|
||||
LanguageIDDwarven = 2 // Dwarven
|
||||
LanguageIDHalfling = 3 // Halfling
|
||||
LanguageIDGnomish = 4 // Gnomish
|
||||
LanguageIDIksar = 5 // Iksar
|
||||
LanguageIDTrollish = 6 // Trollish
|
||||
LanguageIDOgrish = 7 // Ogrish
|
||||
LanguageIDFae = 8 // Fae
|
||||
LanguageIDArasai = 9 // Arasai
|
||||
LanguageIDSarnak = 10 // Sarnak
|
||||
LanguageIDFroglok = 11 // Froglok
|
||||
)
|
||||
|
||||
// Language validation constants
|
||||
@ -34,6 +34,6 @@ const (
|
||||
|
||||
// System limits
|
||||
const (
|
||||
MaxLanguagesPerPlayer = 100 // Reasonable limit to prevent abuse
|
||||
MaxLanguagesPerPlayer = 100 // Reasonable limit to prevent abuse
|
||||
MaxTotalLanguages = 1000 // System-wide language limit
|
||||
)
|
@ -1,5 +1,7 @@
|
||||
package languages
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Database interface for language persistence
|
||||
type Database interface {
|
||||
LoadAllLanguages() ([]*Language, error)
|
||||
@ -69,11 +71,11 @@ type ChatProcessor interface {
|
||||
|
||||
// PlayerLanguageAdapter provides language functionality for players
|
||||
type PlayerLanguageAdapter struct {
|
||||
player *Player
|
||||
languages *PlayerLanguagesList
|
||||
primaryLang int32
|
||||
manager *Manager
|
||||
logger Logger
|
||||
player *Player
|
||||
languages *PlayerLanguagesList
|
||||
primaryLang int32
|
||||
manager *Manager
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// NewPlayerLanguageAdapter creates a new player language adapter
|
||||
|
@ -7,24 +7,24 @@ import (
|
||||
|
||||
// Manager provides high-level management of the language system
|
||||
type Manager struct {
|
||||
masterLanguagesList *MasterLanguagesList
|
||||
database Database
|
||||
logger Logger
|
||||
mutex sync.RWMutex
|
||||
masterLanguagesList *MasterLanguagesList
|
||||
database Database
|
||||
logger Logger
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Statistics
|
||||
languageLookups int64
|
||||
playersWithLanguages int64
|
||||
languageUsageCount map[int32]int64 // Language ID -> usage count
|
||||
languageLookups int64
|
||||
playersWithLanguages int64
|
||||
languageUsageCount map[int32]int64 // Language ID -> usage count
|
||||
}
|
||||
|
||||
// NewManager creates a new language manager
|
||||
func NewManager(database Database, logger Logger) *Manager {
|
||||
return &Manager{
|
||||
masterLanguagesList: NewMasterLanguagesList(),
|
||||
database: database,
|
||||
logger: logger,
|
||||
languageUsageCount: make(map[int32]int64),
|
||||
masterLanguagesList: NewMasterLanguagesList(),
|
||||
database: database,
|
||||
logger: logger,
|
||||
languageUsageCount: make(map[int32]int64),
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,11 +199,11 @@ func (m *Manager) GetStatistics() *LanguageStatistics {
|
||||
}
|
||||
|
||||
return &LanguageStatistics{
|
||||
TotalLanguages: len(allLanguages),
|
||||
PlayersWithLanguages: int(m.playersWithLanguages),
|
||||
LanguageUsageCount: usageCount,
|
||||
LanguageLookups: m.languageLookups,
|
||||
LanguagesByName: languagesByName,
|
||||
TotalLanguages: len(allLanguages),
|
||||
PlayersWithLanguages: int(m.playersWithLanguages),
|
||||
LanguageUsageCount: usageCount,
|
||||
LanguageLookups: m.languageLookups,
|
||||
LanguagesByName: languagesByName,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
|
||||
// Language represents a single language that can be learned by players
|
||||
type Language struct {
|
||||
id int32 // Unique language identifier
|
||||
name string // Language name
|
||||
saveNeeded bool // Whether this language needs to be saved to database
|
||||
id int32 // Unique language identifier
|
||||
name string // Language name
|
||||
saveNeeded bool // Whether this language needs to be saved to database
|
||||
mutex sync.RWMutex // Thread safety
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ func (l *Language) Copy() *Language {
|
||||
|
||||
// MasterLanguagesList manages the global list of all available languages
|
||||
type MasterLanguagesList struct {
|
||||
languages map[int32]*Language // Languages indexed by ID for fast lookup
|
||||
languages map[int32]*Language // Languages indexed by ID for fast lookup
|
||||
nameIndex map[string]*Language // Languages indexed by name for name lookups
|
||||
mutex sync.RWMutex // Thread safety
|
||||
}
|
||||
@ -301,7 +301,7 @@ func (mll *MasterLanguagesList) GetLanguageNames() []string {
|
||||
|
||||
// PlayerLanguagesList manages languages known by a specific player
|
||||
type PlayerLanguagesList struct {
|
||||
languages map[int32]*Language // Player's languages indexed by ID
|
||||
languages map[int32]*Language // Player's languages indexed by ID
|
||||
nameIndex map[string]*Language // Player's languages indexed by name
|
||||
mutex sync.RWMutex // Thread safety
|
||||
}
|
||||
@ -452,9 +452,9 @@ func (pll *PlayerLanguagesList) GetLanguageNames() []string {
|
||||
|
||||
// LanguageStatistics contains language system statistics
|
||||
type LanguageStatistics struct {
|
||||
TotalLanguages int `json:"total_languages"`
|
||||
PlayersWithLanguages int `json:"players_with_languages"`
|
||||
LanguageUsageCount map[int32]int64 `json:"language_usage_count"`
|
||||
LanguageLookups int64 `json:"language_lookups"`
|
||||
LanguagesByName map[string]int32 `json:"languages_by_name"`
|
||||
TotalLanguages int `json:"total_languages"`
|
||||
PlayersWithLanguages int `json:"players_with_languages"`
|
||||
LanguageUsageCount map[int32]int64 `json:"language_usage_count"`
|
||||
LanguageLookups int64 `json:"language_lookups"`
|
||||
LanguagesByName map[string]int32 `json:"languages_by_name"`
|
||||
}
|
@ -60,14 +60,14 @@ type Brain interface {
|
||||
|
||||
// BaseBrain provides the default AI implementation
|
||||
type BaseBrain struct {
|
||||
npc NPC // The NPC this brain controls
|
||||
brainType int8 // Type of brain
|
||||
state *BrainState // Brain state management
|
||||
hateList *HateList // Hate management
|
||||
encounterList *EncounterList // Encounter management
|
||||
statistics *BrainStatistics // Performance statistics
|
||||
logger Logger // Logger interface
|
||||
mutex sync.RWMutex // Thread safety
|
||||
npc NPC // The NPC this brain controls
|
||||
brainType int8 // Type of brain
|
||||
state *BrainState // Brain state management
|
||||
hateList *HateList // Hate management
|
||||
encounterList *EncounterList // Encounter management
|
||||
statistics *BrainStatistics // Performance statistics
|
||||
logger Logger // Logger interface
|
||||
mutex sync.RWMutex // Thread safety
|
||||
}
|
||||
|
||||
// NewBaseBrain creates a new base brain
|
||||
|
@ -2,36 +2,36 @@ package ai
|
||||
|
||||
// AI tick constants
|
||||
const (
|
||||
DefaultThinkTick int32 = 250 // Default think tick in milliseconds (1/4 second)
|
||||
FastThinkTick int32 = 100 // Fast think tick for active AI
|
||||
SlowThinkTick int32 = 1000 // Slow think tick for idle AI
|
||||
BlankBrainTick int32 = 50000 // Very slow tick for blank brain
|
||||
MaxThinkTick int32 = 60000 // Maximum think tick (1 minute)
|
||||
DefaultThinkTick int32 = 250 // Default think tick in milliseconds (1/4 second)
|
||||
FastThinkTick int32 = 100 // Fast think tick for active AI
|
||||
SlowThinkTick int32 = 1000 // Slow think tick for idle AI
|
||||
BlankBrainTick int32 = 50000 // Very slow tick for blank brain
|
||||
MaxThinkTick int32 = 60000 // Maximum think tick (1 minute)
|
||||
)
|
||||
|
||||
// Combat constants
|
||||
const (
|
||||
MaxChaseDistance float32 = 150.0 // Default max chase distance
|
||||
MaxCombatRange float32 = 25.0 // Default max combat range
|
||||
RunbackThreshold float32 = 1.0 // Distance threshold for runback
|
||||
MaxChaseDistance float32 = 150.0 // Default max chase distance
|
||||
MaxCombatRange float32 = 25.0 // Default max combat range
|
||||
RunbackThreshold float32 = 1.0 // Distance threshold for runback
|
||||
)
|
||||
|
||||
// Hate system constants
|
||||
const (
|
||||
MinHateValue int32 = 1 // Minimum hate value (0 or negative is invalid)
|
||||
MaxHateValue int32 = 2147483647 // Maximum hate value (INT_MAX)
|
||||
DefaultHateValue int32 = 100 // Default hate amount
|
||||
MaxHateListSize int = 100 // Maximum entities in hate list
|
||||
MinHateValue int32 = 1 // Minimum hate value (0 or negative is invalid)
|
||||
MaxHateValue int32 = 2147483647 // Maximum hate value (INT_MAX)
|
||||
DefaultHateValue int32 = 100 // Default hate amount
|
||||
MaxHateListSize int = 100 // Maximum entities in hate list
|
||||
)
|
||||
|
||||
// Encounter system constants
|
||||
const (
|
||||
MaxEncounterSize int = 50 // Maximum entities in encounter list
|
||||
MaxEncounterSize int = 50 // Maximum entities in encounter list
|
||||
)
|
||||
|
||||
// Spell recovery constants
|
||||
const (
|
||||
SpellRecoveryBuffer int32 = 2000 // Additional recovery time buffer (2 seconds)
|
||||
SpellRecoveryBuffer int32 = 2000 // Additional recovery time buffer (2 seconds)
|
||||
)
|
||||
|
||||
// Brain type constants for identification
|
||||
@ -46,9 +46,9 @@ const (
|
||||
|
||||
// Pet movement constants
|
||||
const (
|
||||
PetMovementFollow int8 = 0
|
||||
PetMovementStay int8 = 1
|
||||
PetMovementGuard int8 = 2
|
||||
PetMovementFollow int8 = 0
|
||||
PetMovementStay int8 = 1
|
||||
PetMovementGuard int8 = 2
|
||||
)
|
||||
|
||||
// Encounter state constants
|
||||
@ -60,31 +60,31 @@ const (
|
||||
|
||||
// Combat decision constants
|
||||
const (
|
||||
MeleeAttackChance int = 70 // Base chance for melee attack
|
||||
SpellCastChance int = 30 // Base chance for spell casting
|
||||
BuffCheckChance int = 50 // Chance to check for buffs
|
||||
MeleeAttackChance int = 70 // Base chance for melee attack
|
||||
SpellCastChance int = 30 // Base chance for spell casting
|
||||
BuffCheckChance int = 50 // Chance to check for buffs
|
||||
)
|
||||
|
||||
// AI state flags
|
||||
const (
|
||||
AIStateIdle int32 = 0
|
||||
AIStateCombat int32 = 1
|
||||
AIStateFollowing int32 = 2
|
||||
AIStateRunback int32 = 3
|
||||
AIStateCasting int32 = 4
|
||||
AIStateMoving int32 = 5
|
||||
AIStateIdle int32 = 0
|
||||
AIStateCombat int32 = 1
|
||||
AIStateFollowing int32 = 2
|
||||
AIStateRunback int32 = 3
|
||||
AIStateCasting int32 = 4
|
||||
AIStateMoving int32 = 5
|
||||
)
|
||||
|
||||
// Debug levels
|
||||
const (
|
||||
DebugLevelNone int8 = 0
|
||||
DebugLevelBasic int8 = 1
|
||||
DebugLevelDetailed int8 = 2
|
||||
DebugLevelVerbose int8 = 3
|
||||
DebugLevelNone int8 = 0
|
||||
DebugLevelBasic int8 = 1
|
||||
DebugLevelDetailed int8 = 2
|
||||
DebugLevelVerbose int8 = 3
|
||||
)
|
||||
|
||||
// Timer constants
|
||||
const (
|
||||
MillisecondsPerSecond int32 = 1000
|
||||
RecoveryTimeMultiple int32 = 10 // Multiply cast/recovery times by 10
|
||||
RecoveryTimeMultiple int32 = 10 // Multiply cast/recovery times by 10
|
||||
)
|
@ -1,6 +1,9 @@
|
||||
package ai
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Logger interface for AI logging
|
||||
type Logger interface {
|
||||
@ -146,11 +149,11 @@ type Zone interface {
|
||||
|
||||
// AIManager provides high-level management of the AI system
|
||||
type AIManager struct {
|
||||
brains map[int32]Brain // Map of NPC ID to brain
|
||||
activeCount int64 // Number of active brains
|
||||
totalThinks int64 // Total think cycles processed
|
||||
logger Logger // Logger for AI events
|
||||
luaInterface LuaInterface // Lua script interface
|
||||
brains map[int32]Brain // Map of NPC ID to brain
|
||||
activeCount int64 // Number of active brains
|
||||
totalThinks int64 // Total think cycles processed
|
||||
logger Logger // Logger for AI events
|
||||
luaInterface LuaInterface // Lua script interface
|
||||
}
|
||||
|
||||
// NewAIManager creates a new AI manager
|
||||
@ -325,10 +328,10 @@ func (am *AIManager) ClearAllBrains() {
|
||||
// GetStatistics returns overall AI system statistics
|
||||
func (am *AIManager) GetStatistics() *AIStatistics {
|
||||
return &AIStatistics{
|
||||
TotalBrains: len(am.brains),
|
||||
ActiveBrains: int(am.activeCount),
|
||||
TotalThinks: am.totalThinks,
|
||||
BrainsByType: am.getBrainCountsByType(),
|
||||
TotalBrains: len(am.brains),
|
||||
ActiveBrains: int(am.activeCount),
|
||||
TotalThinks: am.totalThinks,
|
||||
BrainsByType: am.getBrainCountsByType(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,10 +349,10 @@ func (am *AIManager) getBrainCountsByType() map[string]int {
|
||||
|
||||
// AIStatistics contains AI system statistics
|
||||
type AIStatistics struct {
|
||||
TotalBrains int `json:"total_brains"`
|
||||
ActiveBrains int `json:"active_brains"`
|
||||
TotalThinks int64 `json:"total_thinks"`
|
||||
BrainsByType map[string]int `json:"brains_by_type"`
|
||||
TotalBrains int `json:"total_brains"`
|
||||
ActiveBrains int `json:"active_brains"`
|
||||
TotalThinks int64 `json:"total_thinks"`
|
||||
BrainsByType map[string]int `json:"brains_by_type"`
|
||||
}
|
||||
|
||||
// AIBrainAdapter provides NPC functionality for brains
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
|
||||
// HateEntry represents a single hate entry in the hate list
|
||||
type HateEntry struct {
|
||||
EntityID int32 // ID of the hated entity
|
||||
HateValue int32 // Amount of hate (must be >= 1)
|
||||
LastUpdated int64 // Timestamp of last hate update
|
||||
EntityID int32 // ID of the hated entity
|
||||
HateValue int32 // Amount of hate (must be >= 1)
|
||||
LastUpdated int64 // Timestamp of last hate update
|
||||
}
|
||||
|
||||
// NewHateEntry creates a new hate entry
|
||||
@ -27,8 +27,8 @@ func NewHateEntry(entityID, hateValue int32) *HateEntry {
|
||||
|
||||
// HateList manages the hate list for an NPC brain
|
||||
type HateList struct {
|
||||
entries map[int32]*HateEntry // Map of entity ID to hate entry
|
||||
mutex sync.RWMutex // Thread safety
|
||||
entries map[int32]*HateEntry // Map of entity ID to hate entry
|
||||
mutex sync.RWMutex // Thread safety
|
||||
}
|
||||
|
||||
// NewHateList creates a new hate list
|
||||
@ -180,11 +180,11 @@ func (hl *HateList) removeOldestEntry() {
|
||||
|
||||
// EncounterEntry represents a single encounter participant
|
||||
type EncounterEntry struct {
|
||||
EntityID int32 // ID of the entity
|
||||
CharacterID int32 // Character ID for players (0 for NPCs)
|
||||
AddedTime int64 // When entity was added to encounter
|
||||
IsPlayer bool // Whether this is a player entity
|
||||
IsBot bool // Whether this is a bot entity
|
||||
EntityID int32 // ID of the entity
|
||||
CharacterID int32 // Character ID for players (0 for NPCs)
|
||||
AddedTime int64 // When entity was added to encounter
|
||||
IsPlayer bool // Whether this is a player entity
|
||||
IsBot bool // Whether this is a bot entity
|
||||
}
|
||||
|
||||
// NewEncounterEntry creates a new encounter entry
|
||||
@ -200,10 +200,10 @@ func NewEncounterEntry(entityID, characterID int32, isPlayer, isBot bool) *Encou
|
||||
|
||||
// EncounterList manages the encounter list for an NPC brain
|
||||
type EncounterList struct {
|
||||
entries map[int32]*EncounterEntry // Map of entity ID to encounter entry
|
||||
playerEntries map[int32]int32 // Map of character ID to entity ID
|
||||
playerInEncounter bool // Whether any player is in encounter
|
||||
mutex sync.RWMutex // Thread safety
|
||||
entries map[int32]*EncounterEntry // Map of entity ID to encounter entry
|
||||
playerEntries map[int32]int32 // Map of character ID to entity ID
|
||||
playerInEncounter bool // Whether any player is in encounter
|
||||
mutex sync.RWMutex // Thread safety
|
||||
}
|
||||
|
||||
// NewEncounterList creates a new encounter list
|
||||
@ -356,13 +356,13 @@ func (el *EncounterList) updatePlayerInEncounter() {
|
||||
|
||||
// BrainState represents the current state of a brain
|
||||
type BrainState struct {
|
||||
State int32 // Current AI state
|
||||
LastThink int64 // Timestamp of last think cycle
|
||||
ThinkTick int32 // Time between think cycles in milliseconds
|
||||
SpellRecovery int64 // Timestamp when spell recovery completes
|
||||
IsActive bool // Whether the brain is active
|
||||
DebugLevel int8 // Debug output level
|
||||
mutex sync.RWMutex
|
||||
State int32 // Current AI state
|
||||
LastThink int64 // Timestamp of last think cycle
|
||||
ThinkTick int32 // Time between think cycles in milliseconds
|
||||
SpellRecovery int64 // Timestamp when spell recovery completes
|
||||
IsActive bool // Whether the brain is active
|
||||
DebugLevel int8 // Debug output level
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewBrainState creates a new brain state
|
||||
@ -479,26 +479,26 @@ func (bs *BrainState) SetDebugLevel(level int8) {
|
||||
|
||||
// BrainStatistics contains brain performance statistics
|
||||
type BrainStatistics struct {
|
||||
ThinkCycles int64 `json:"think_cycles"`
|
||||
SpellsCast int64 `json:"spells_cast"`
|
||||
MeleeAttacks int64 `json:"melee_attacks"`
|
||||
HateEvents int64 `json:"hate_events"`
|
||||
EncounterEvents int64 `json:"encounter_events"`
|
||||
AverageThinkTime float64 `json:"average_think_time_ms"`
|
||||
LastThinkTime int64 `json:"last_think_time"`
|
||||
TotalActiveTime int64 `json:"total_active_time_ms"`
|
||||
ThinkCycles int64 `json:"think_cycles"`
|
||||
SpellsCast int64 `json:"spells_cast"`
|
||||
MeleeAttacks int64 `json:"melee_attacks"`
|
||||
HateEvents int64 `json:"hate_events"`
|
||||
EncounterEvents int64 `json:"encounter_events"`
|
||||
AverageThinkTime float64 `json:"average_think_time_ms"`
|
||||
LastThinkTime int64 `json:"last_think_time"`
|
||||
TotalActiveTime int64 `json:"total_active_time_ms"`
|
||||
}
|
||||
|
||||
// NewBrainStatistics creates new brain statistics
|
||||
func NewBrainStatistics() *BrainStatistics {
|
||||
return &BrainStatistics{
|
||||
ThinkCycles: 0,
|
||||
SpellsCast: 0,
|
||||
MeleeAttacks: 0,
|
||||
HateEvents: 0,
|
||||
EncounterEvents: 0,
|
||||
AverageThinkTime: 0.0,
|
||||
LastThinkTime: time.Now().UnixMilli(),
|
||||
TotalActiveTime: 0,
|
||||
ThinkCycles: 0,
|
||||
SpellsCast: 0,
|
||||
MeleeAttacks: 0,
|
||||
HateEvents: 0,
|
||||
EncounterEvents: 0,
|
||||
AverageThinkTime: 0.0,
|
||||
LastThinkTime: time.Now().UnixMilli(),
|
||||
TotalActiveTime: 0,
|
||||
}
|
||||
}
|
@ -2,9 +2,9 @@ package npc
|
||||
|
||||
// AI Strategy constants
|
||||
const (
|
||||
AIStrategyBalanced int8 = 1
|
||||
AIStrategyOffensive int8 = 2
|
||||
AIStrategyDefensive int8 = 3
|
||||
AIStrategyBalanced int8 = 1
|
||||
AIStrategyOffensive int8 = 2
|
||||
AIStrategyDefensive int8 = 3
|
||||
)
|
||||
|
||||
// Randomize Appearances constants
|
||||
@ -47,26 +47,26 @@ const (
|
||||
|
||||
// Cast Type constants
|
||||
const (
|
||||
CastOnSpawn int8 = 0
|
||||
CastOnAggro int8 = 1
|
||||
CastOnSpawn int8 = 0
|
||||
CastOnAggro int8 = 1
|
||||
MaxCastTypes int8 = 2
|
||||
)
|
||||
|
||||
// Default values
|
||||
const (
|
||||
DefaultCastPercentage int8 = 25
|
||||
DefaultAggroRadius float32 = 10.0
|
||||
DefaultRunbackSpeed float32 = 2.0
|
||||
MaxSkillBonuses int = 100
|
||||
MaxNPCSpells int = 50
|
||||
MaxPauseTime int32 = 300000 // 5 minutes max pause
|
||||
DefaultCastPercentage int8 = 25
|
||||
DefaultAggroRadius float32 = 10.0
|
||||
DefaultRunbackSpeed float32 = 2.0
|
||||
MaxSkillBonuses int = 100
|
||||
MaxNPCSpells int = 50
|
||||
MaxPauseTime int32 = 300000 // 5 minutes max pause
|
||||
)
|
||||
|
||||
// NPC validation constants
|
||||
const (
|
||||
MinNPCLevel int8 = 1
|
||||
MaxNPCLevel int8 = 100
|
||||
MaxNPCNameLen int = 64
|
||||
MinNPCLevel int8 = 1
|
||||
MaxNPCLevel int8 = 100
|
||||
MaxNPCNameLen int = 64
|
||||
MinAppearanceID int32 = 0
|
||||
MaxAppearanceID int32 = 999999
|
||||
)
|
||||
|
@ -506,9 +506,9 @@ func (ca *CombatAdapter) ProcessCombat() error {
|
||||
|
||||
// MovementAdapter provides movement functionality for NPCs
|
||||
type MovementAdapter struct {
|
||||
npc *NPC
|
||||
movementManager MovementManager
|
||||
logger Logger
|
||||
npc *NPC
|
||||
movementManager MovementManager
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// NewMovementAdapter creates a new movement adapter
|
||||
|
@ -10,23 +10,23 @@ import (
|
||||
|
||||
// Manager provides high-level management of the NPC system
|
||||
type Manager struct {
|
||||
npcs map[int32]*NPC // NPCs indexed by ID
|
||||
npcsByZone map[int32][]*NPC // NPCs indexed by zone ID
|
||||
npcsByAppearance map[int32][]*NPC // NPCs indexed by appearance ID
|
||||
database Database // Database interface
|
||||
logger Logger // Logger interface
|
||||
spellManager SpellManager // Spell system interface
|
||||
skillManager SkillManager // Skill system interface
|
||||
appearanceManager AppearanceManager // Appearance system interface
|
||||
mutex sync.RWMutex // Thread safety
|
||||
npcs map[int32]*NPC // NPCs indexed by ID
|
||||
npcsByZone map[int32][]*NPC // NPCs indexed by zone ID
|
||||
npcsByAppearance map[int32][]*NPC // NPCs indexed by appearance ID
|
||||
database Database // Database interface
|
||||
logger Logger // Logger interface
|
||||
spellManager SpellManager // Spell system interface
|
||||
skillManager SkillManager // Skill system interface
|
||||
appearanceManager AppearanceManager // Appearance system interface
|
||||
mutex sync.RWMutex // Thread safety
|
||||
|
||||
// Statistics
|
||||
totalNPCs int64
|
||||
npcsInCombat int64
|
||||
spellCastCount int64
|
||||
skillUsageCount int64
|
||||
runbackCount int64
|
||||
aiStrategyCounts map[int8]int64
|
||||
totalNPCs int64
|
||||
npcsInCombat int64
|
||||
spellCastCount int64
|
||||
skillUsageCount int64
|
||||
runbackCount int64
|
||||
aiStrategyCounts map[int8]int64
|
||||
|
||||
// Configuration
|
||||
maxNPCs int32
|
||||
|
@ -15,35 +15,35 @@ import (
|
||||
// NewNPC creates a new NPC with default values
|
||||
func NewNPC() *NPC {
|
||||
npc := &NPC{
|
||||
Entity: entity.NewEntity(),
|
||||
appearanceID: 0,
|
||||
npcID: 0,
|
||||
aiStrategy: AIStrategyBalanced,
|
||||
attackType: 0,
|
||||
castPercentage: DefaultCastPercentage,
|
||||
maxPetLevel: DefaultMaxPetLevel,
|
||||
aggroRadius: DefaultAggroRadius,
|
||||
baseAggroRadius: DefaultAggroRadius,
|
||||
runback: nil,
|
||||
runningBack: false,
|
||||
runbackHeadingDir1: 0,
|
||||
runbackHeadingDir2: 0,
|
||||
pauseTimer: NewTimer(),
|
||||
primarySpellList: 0,
|
||||
secondarySpellList: 0,
|
||||
primarySkillList: 0,
|
||||
secondarySkillList: 0,
|
||||
equipmentListID: 0,
|
||||
skills: make(map[string]*Skill),
|
||||
spells: make([]*NPCSpell, 0),
|
||||
castOnSpells: make(map[int8][]*NPCSpell),
|
||||
skillBonuses: make(map[int32]*SkillBonus),
|
||||
hasSpells: false,
|
||||
castOnAggroCompleted: false,
|
||||
shardID: 0,
|
||||
shardCharID: 0,
|
||||
shardCreatedTimestamp: 0,
|
||||
callRunback: false,
|
||||
Entity: entity.NewEntity(),
|
||||
appearanceID: 0,
|
||||
npcID: 0,
|
||||
aiStrategy: AIStrategyBalanced,
|
||||
attackType: 0,
|
||||
castPercentage: DefaultCastPercentage,
|
||||
maxPetLevel: DefaultMaxPetLevel,
|
||||
aggroRadius: DefaultAggroRadius,
|
||||
baseAggroRadius: DefaultAggroRadius,
|
||||
runback: nil,
|
||||
runningBack: false,
|
||||
runbackHeadingDir1: 0,
|
||||
runbackHeadingDir2: 0,
|
||||
pauseTimer: NewTimer(),
|
||||
primarySpellList: 0,
|
||||
secondarySpellList: 0,
|
||||
primarySkillList: 0,
|
||||
secondarySkillList: 0,
|
||||
equipmentListID: 0,
|
||||
skills: make(map[string]*Skill),
|
||||
spells: make([]*NPCSpell, 0),
|
||||
castOnSpells: make(map[int8][]*NPCSpell),
|
||||
skillBonuses: make(map[int32]*SkillBonus),
|
||||
hasSpells: false,
|
||||
castOnAggroCompleted: false,
|
||||
shardID: 0,
|
||||
shardCharID: 0,
|
||||
shardCreatedTimestamp: 0,
|
||||
callRunback: false,
|
||||
}
|
||||
|
||||
// Initialize cast-on spell arrays
|
||||
|
@ -11,24 +11,24 @@ import (
|
||||
|
||||
// NPCSpell represents a spell configuration for NPCs
|
||||
type NPCSpell struct {
|
||||
ListID int32 // Spell list identifier
|
||||
SpellID int32 // Spell ID from master spell list
|
||||
Tier int8 // Spell tier
|
||||
CastOnSpawn bool // Cast when NPC spawns
|
||||
CastOnInitialAggro bool // Cast when first entering combat
|
||||
RequiredHPRatio int8 // HP ratio requirement for casting (-100 to 100)
|
||||
mutex sync.RWMutex
|
||||
ListID int32 // Spell list identifier
|
||||
SpellID int32 // Spell ID from master spell list
|
||||
Tier int8 // Spell tier
|
||||
CastOnSpawn bool // Cast when NPC spawns
|
||||
CastOnInitialAggro bool // Cast when first entering combat
|
||||
RequiredHPRatio int8 // HP ratio requirement for casting (-100 to 100)
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewNPCSpell creates a new NPCSpell
|
||||
func NewNPCSpell() *NPCSpell {
|
||||
return &NPCSpell{
|
||||
ListID: 0,
|
||||
SpellID: 0,
|
||||
Tier: 1,
|
||||
CastOnSpawn: false,
|
||||
CastOnInitialAggro: false,
|
||||
RequiredHPRatio: 0,
|
||||
ListID: 0,
|
||||
SpellID: 0,
|
||||
Tier: 1,
|
||||
CastOnSpawn: false,
|
||||
CastOnInitialAggro: false,
|
||||
RequiredHPRatio: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,12 +38,12 @@ func (ns *NPCSpell) Copy() *NPCSpell {
|
||||
defer ns.mutex.RUnlock()
|
||||
|
||||
return &NPCSpell{
|
||||
ListID: ns.ListID,
|
||||
SpellID: ns.SpellID,
|
||||
Tier: ns.Tier,
|
||||
CastOnSpawn: ns.CastOnSpawn,
|
||||
CastOnInitialAggro: ns.CastOnInitialAggro,
|
||||
RequiredHPRatio: ns.RequiredHPRatio,
|
||||
ListID: ns.ListID,
|
||||
SpellID: ns.SpellID,
|
||||
Tier: ns.Tier,
|
||||
CastOnSpawn: ns.CastOnSpawn,
|
||||
CastOnInitialAggro: ns.CastOnInitialAggro,
|
||||
RequiredHPRatio: ns.RequiredHPRatio,
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,8 +123,8 @@ func (ns *NPCSpell) SetRequiredHPRatio(ratio int8) {
|
||||
|
||||
// SkillBonus represents a skill bonus from spells
|
||||
type SkillBonus struct {
|
||||
SpellID int32 // Spell providing the bonus
|
||||
Skills map[int32]*SkillBonusValue // Map of skill ID to bonus value
|
||||
SpellID int32 // Spell providing the bonus
|
||||
Skills map[int32]*SkillBonusValue // Map of skill ID to bonus value
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
@ -222,60 +222,60 @@ func (ml *MovementLocation) Copy() *MovementLocation {
|
||||
|
||||
// NPC represents a non-player character extending Entity
|
||||
type NPC struct {
|
||||
*entity.Entity // Embedded entity for combat capabilities
|
||||
*entity.Entity // Embedded entity for combat capabilities
|
||||
|
||||
// Core NPC properties
|
||||
appearanceID int32 // Appearance ID for client display
|
||||
npcID int32 // NPC database ID
|
||||
aiStrategy int8 // AI strategy (balanced/offensive/defensive)
|
||||
attackType int8 // Attack type preference
|
||||
castPercentage int8 // Percentage chance to cast spells
|
||||
maxPetLevel int8 // Maximum pet level
|
||||
appearanceID int32 // Appearance ID for client display
|
||||
npcID int32 // NPC database ID
|
||||
aiStrategy int8 // AI strategy (balanced/offensive/defensive)
|
||||
attackType int8 // Attack type preference
|
||||
castPercentage int8 // Percentage chance to cast spells
|
||||
maxPetLevel int8 // Maximum pet level
|
||||
|
||||
// Combat and movement
|
||||
aggroRadius float32 // Aggro detection radius
|
||||
baseAggroRadius float32 // Base aggro radius (for resets)
|
||||
runback *MovementLocation // Runback location when leaving combat
|
||||
runningBack bool // Currently running back to spawn point
|
||||
runbackHeadingDir1 int16 // Original heading direction 1
|
||||
runbackHeadingDir2 int16 // Original heading direction 2
|
||||
pauseTimer *Timer // Movement pause timer
|
||||
aggroRadius float32 // Aggro detection radius
|
||||
baseAggroRadius float32 // Base aggro radius (for resets)
|
||||
runback *MovementLocation // Runback location when leaving combat
|
||||
runningBack bool // Currently running back to spawn point
|
||||
runbackHeadingDir1 int16 // Original heading direction 1
|
||||
runbackHeadingDir2 int16 // Original heading direction 2
|
||||
pauseTimer *Timer // Movement pause timer
|
||||
|
||||
// Spell and skill management
|
||||
primarySpellList int32 // Primary spell list ID
|
||||
secondarySpellList int32 // Secondary spell list ID
|
||||
primarySkillList int32 // Primary skill list ID
|
||||
secondarySkillList int32 // Secondary skill list ID
|
||||
equipmentListID int32 // Equipment list ID
|
||||
skills map[string]*Skill // NPC skills by name
|
||||
spells []*NPCSpell // Available spells
|
||||
castOnSpells map[int8][]*NPCSpell // Spells to cast by trigger type
|
||||
skillBonuses map[int32]*SkillBonus // Skill bonuses from spells
|
||||
hasSpells bool // Whether NPC has any spells
|
||||
castOnAggroCompleted bool // Whether cast-on-aggro spells are done
|
||||
primarySpellList int32 // Primary spell list ID
|
||||
secondarySpellList int32 // Secondary spell list ID
|
||||
primarySkillList int32 // Primary skill list ID
|
||||
secondarySkillList int32 // Secondary skill list ID
|
||||
equipmentListID int32 // Equipment list ID
|
||||
skills map[string]*Skill // NPC skills by name
|
||||
spells []*NPCSpell // Available spells
|
||||
castOnSpells map[int8][]*NPCSpell // Spells to cast by trigger type
|
||||
skillBonuses map[int32]*SkillBonus // Skill bonuses from spells
|
||||
hasSpells bool // Whether NPC has any spells
|
||||
castOnAggroCompleted bool // Whether cast-on-aggro spells are done
|
||||
|
||||
// Brain/AI system (placeholder for now)
|
||||
brain Brain // AI brain for decision making
|
||||
brain Brain // AI brain for decision making
|
||||
|
||||
// Shard system (for cross-server functionality)
|
||||
shardID int32 // Shard identifier
|
||||
shardCharID int32 // Character ID on shard
|
||||
shardCreatedTimestamp int64 // Timestamp when created on shard
|
||||
shardID int32 // Shard identifier
|
||||
shardCharID int32 // Character ID on shard
|
||||
shardCreatedTimestamp int64 // Timestamp when created on shard
|
||||
|
||||
// Thread safety
|
||||
mutex sync.RWMutex // Main NPC mutex
|
||||
brainMutex sync.RWMutex // Brain-specific mutex
|
||||
mutex sync.RWMutex // Main NPC mutex
|
||||
brainMutex sync.RWMutex // Brain-specific mutex
|
||||
|
||||
// Atomic flags for thread-safe state management
|
||||
callRunback bool // Flag to trigger runback
|
||||
callRunback bool // Flag to trigger runback
|
||||
}
|
||||
|
||||
// Timer represents a simple timer for NPC operations
|
||||
type Timer struct {
|
||||
duration time.Duration
|
||||
startTime time.Time
|
||||
enabled bool
|
||||
mutex sync.RWMutex
|
||||
duration time.Duration
|
||||
startTime time.Time
|
||||
enabled bool
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewTimer creates a new timer
|
||||
@ -325,10 +325,10 @@ func (t *Timer) Disable() {
|
||||
|
||||
// Skill represents an NPC skill (simplified from C++ version)
|
||||
type Skill struct {
|
||||
SkillID int32 // Skill identifier
|
||||
Name string // Skill name
|
||||
CurrentVal int16 // Current skill value
|
||||
MaxVal int16 // Maximum skill value
|
||||
SkillID int32 // Skill identifier
|
||||
Name string // Skill name
|
||||
CurrentVal int16 // Current skill value
|
||||
MaxVal int16 // Maximum skill value
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
@ -428,13 +428,13 @@ func (b *DefaultBrain) SetActive(active bool) {
|
||||
|
||||
// NPCStatistics contains NPC system statistics
|
||||
type NPCStatistics struct {
|
||||
TotalNPCs int `json:"total_npcs"`
|
||||
NPCsInCombat int `json:"npcs_in_combat"`
|
||||
NPCsWithSpells int `json:"npcs_with_spells"`
|
||||
NPCsWithSkills int `json:"npcs_with_skills"`
|
||||
AIStrategyCounts map[string]int `json:"ai_strategy_counts"`
|
||||
SpellCastCount int64 `json:"spell_cast_count"`
|
||||
SkillUsageCount int64 `json:"skill_usage_count"`
|
||||
RunbackCount int64 `json:"runback_count"`
|
||||
AverageAggroRadius float32 `json:"average_aggro_radius"`
|
||||
TotalNPCs int `json:"total_npcs"`
|
||||
NPCsInCombat int `json:"npcs_in_combat"`
|
||||
NPCsWithSpells int `json:"npcs_with_spells"`
|
||||
NPCsWithSkills int `json:"npcs_with_skills"`
|
||||
AIStrategyCounts map[string]int `json:"ai_strategy_counts"`
|
||||
SpellCastCount int64 `json:"spell_cast_count"`
|
||||
SkillUsageCount int64 `json:"skill_usage_count"`
|
||||
RunbackCount int64 `json:"runback_count"`
|
||||
AverageAggroRadius float32 `json:"average_aggro_radius"`
|
||||
}
|
@ -7,8 +7,8 @@ const (
|
||||
|
||||
// Object appearance defaults (from C++ constructor)
|
||||
ObjectActivityStatus = 64 // Default activity status
|
||||
ObjectPosState = 1 // Default position state
|
||||
ObjectDifficulty = 0 // Default difficulty
|
||||
ObjectPosState = 1 // Default position state
|
||||
ObjectDifficulty = 0 // Default difficulty
|
||||
|
||||
// Object interaction constants
|
||||
ObjectShowCommandIcon = 1 // Show command icon when interactable
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"eq2emu/internal/spawn"
|
||||
"eq2emu/internal/common"
|
||||
)
|
||||
|
||||
// ObjectSpawn represents an object that extends spawn functionality
|
||||
@ -13,8 +12,8 @@ type ObjectSpawn struct {
|
||||
*spawn.Spawn // Embed the spawn functionality
|
||||
|
||||
// Object-specific properties
|
||||
clickable bool // Whether the object can be clicked/interacted with
|
||||
deviceID int8 // Device ID for interactive objects
|
||||
clickable bool // Whether the object can be clicked/interacted with
|
||||
deviceID int8 // Device ID for interactive objects
|
||||
}
|
||||
|
||||
// NewObjectSpawn creates a new object spawn with default values
|
||||
@ -145,7 +144,7 @@ func (os *ObjectSpawn) GetObjectInfo() map[string]interface{} {
|
||||
|
||||
// ObjectSpawnManager manages object spawns specifically
|
||||
type ObjectSpawnManager struct {
|
||||
spawnManager *spawn.SpawnManager // Reference to global spawn manager
|
||||
spawnManager *spawn.SpawnManager // Reference to global spawn manager
|
||||
objects map[int32]*ObjectSpawn // Object spawns by spawn ID
|
||||
}
|
||||
|
||||
|
@ -167,14 +167,14 @@ func (osae *ObjectSpawnAsEntity) SetClientVersion(version int32) {
|
||||
|
||||
// ObjectItem represents an item provided by an object (merchants, containers, etc.)
|
||||
type ObjectItem struct {
|
||||
id int32
|
||||
name string
|
||||
quantity int32
|
||||
iconID int32
|
||||
noTrade bool
|
||||
heirloom bool
|
||||
attuned bool
|
||||
creationTime time.Time
|
||||
id int32
|
||||
name string
|
||||
quantity int32
|
||||
iconID int32
|
||||
noTrade bool
|
||||
heirloom bool
|
||||
attuned bool
|
||||
creationTime time.Time
|
||||
groupCharacterIDs []int32
|
||||
}
|
||||
|
||||
|
@ -19,33 +19,33 @@ type Object struct {
|
||||
deviceID int8 // Device ID for interactive objects
|
||||
|
||||
// Inherited spawn properties (placeholder until spawn integration)
|
||||
databaseID int32
|
||||
size int16
|
||||
sizeOffset int8
|
||||
merchantID int32
|
||||
merchantType int8
|
||||
merchantMinLevel int8
|
||||
merchantMaxLevel int8
|
||||
isCollector bool
|
||||
factionID int32
|
||||
totalHP int32
|
||||
totalPower int32
|
||||
currentHP int32
|
||||
currentPower int32
|
||||
transporterID int32
|
||||
soundsDisabled bool
|
||||
omittedByDBFlag bool
|
||||
lootTier int8
|
||||
lootDropType int8
|
||||
spawnScript string
|
||||
spawnScriptSetDB bool
|
||||
primaryCommandListID int32
|
||||
secondaryCommandListID int32
|
||||
databaseID int32
|
||||
size int16
|
||||
sizeOffset int8
|
||||
merchantID int32
|
||||
merchantType int8
|
||||
merchantMinLevel int8
|
||||
merchantMaxLevel int8
|
||||
isCollector bool
|
||||
factionID int32
|
||||
totalHP int32
|
||||
totalPower int32
|
||||
currentHP int32
|
||||
currentPower int32
|
||||
transporterID int32
|
||||
soundsDisabled bool
|
||||
omittedByDBFlag bool
|
||||
lootTier int8
|
||||
lootDropType int8
|
||||
spawnScript string
|
||||
spawnScriptSetDB bool
|
||||
primaryCommandListID int32
|
||||
secondaryCommandListID int32
|
||||
|
||||
// Appearance data placeholder - TODO: Use actual appearance struct
|
||||
appearanceActivityStatus int8
|
||||
appearancePosState int8
|
||||
appearanceDifficulty int8
|
||||
appearanceActivityStatus int8
|
||||
appearancePosState int8
|
||||
appearanceDifficulty int8
|
||||
appearanceShowCommandIcon int8
|
||||
|
||||
// Command lists - TODO: Use actual command structures
|
||||
@ -60,15 +60,15 @@ type Object struct {
|
||||
// Converted from C++ Object::Object constructor
|
||||
func NewObject() *Object {
|
||||
return &Object{
|
||||
clickable: false,
|
||||
zoneName: "",
|
||||
deviceID: DeviceIDNone,
|
||||
appearanceActivityStatus: ObjectActivityStatus,
|
||||
appearancePosState: ObjectPosState,
|
||||
appearanceDifficulty: ObjectDifficulty,
|
||||
clickable: false,
|
||||
zoneName: "",
|
||||
deviceID: DeviceIDNone,
|
||||
appearanceActivityStatus: ObjectActivityStatus,
|
||||
appearancePosState: ObjectPosState,
|
||||
appearanceDifficulty: ObjectDifficulty,
|
||||
appearanceShowCommandIcon: 0,
|
||||
primaryCommands: make([]string, 0),
|
||||
secondaryCommands: make([]string, 0),
|
||||
primaryCommands: make([]string, 0),
|
||||
secondaryCommands: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,97 +2,97 @@ package player
|
||||
|
||||
// Character flag constants
|
||||
const (
|
||||
CF_COMBAT_EXPERIENCE_ENABLED = 0
|
||||
CF_ENABLE_CHANGE_LASTNAME = 1
|
||||
CF_FOOD_AUTO_CONSUME = 2
|
||||
CF_DRINK_AUTO_CONSUME = 3
|
||||
CF_AUTO_ATTACK = 4
|
||||
CF_RANGED_AUTO_ATTACK = 5
|
||||
CF_QUEST_EXPERIENCE_ENABLED = 6
|
||||
CF_CHASE_CAMERA_MAYBE = 7
|
||||
CF_100 = 8
|
||||
CF_200 = 9
|
||||
CF_IS_SITTING = 10 // Can't cast or attack
|
||||
CF_800 = 11
|
||||
CF_ANONYMOUS = 12
|
||||
CF_ROLEPLAYING = 13
|
||||
CF_AFK = 14
|
||||
CF_LFG = 15
|
||||
CF_LFW = 16
|
||||
CF_HIDE_HOOD = 17
|
||||
CF_HIDE_HELM = 18
|
||||
CF_SHOW_ILLUSION = 19
|
||||
CF_ALLOW_DUEL_INVITES = 20
|
||||
CF_ALLOW_TRADE_INVITES = 21
|
||||
CF_ALLOW_GROUP_INVITES = 22
|
||||
CF_ALLOW_RAID_INVITES = 23
|
||||
CF_ALLOW_GUILD_INVITES = 24
|
||||
CF_2000000 = 25
|
||||
CF_4000000 = 26
|
||||
CF_DEFENSE_SKILLS_AT_MAX_QUESTIONABLE = 27
|
||||
CF_SHOW_GUILD_HERALDRY = 28
|
||||
CF_SHOW_CLOAK = 29
|
||||
CF_IN_PVP = 30
|
||||
CF_IS_HATED = 31
|
||||
CF2_1 = 32
|
||||
CF2_2 = 33
|
||||
CF2_4 = 34
|
||||
CF2_ALLOW_LON_INVITES = 35
|
||||
CF2_SHOW_RANGED = 36
|
||||
CF2_ALLOW_VOICE_INVITES = 37
|
||||
CF_COMBAT_EXPERIENCE_ENABLED = 0
|
||||
CF_ENABLE_CHANGE_LASTNAME = 1
|
||||
CF_FOOD_AUTO_CONSUME = 2
|
||||
CF_DRINK_AUTO_CONSUME = 3
|
||||
CF_AUTO_ATTACK = 4
|
||||
CF_RANGED_AUTO_ATTACK = 5
|
||||
CF_QUEST_EXPERIENCE_ENABLED = 6
|
||||
CF_CHASE_CAMERA_MAYBE = 7
|
||||
CF_100 = 8
|
||||
CF_200 = 9
|
||||
CF_IS_SITTING = 10 // Can't cast or attack
|
||||
CF_800 = 11
|
||||
CF_ANONYMOUS = 12
|
||||
CF_ROLEPLAYING = 13
|
||||
CF_AFK = 14
|
||||
CF_LFG = 15
|
||||
CF_LFW = 16
|
||||
CF_HIDE_HOOD = 17
|
||||
CF_HIDE_HELM = 18
|
||||
CF_SHOW_ILLUSION = 19
|
||||
CF_ALLOW_DUEL_INVITES = 20
|
||||
CF_ALLOW_TRADE_INVITES = 21
|
||||
CF_ALLOW_GROUP_INVITES = 22
|
||||
CF_ALLOW_RAID_INVITES = 23
|
||||
CF_ALLOW_GUILD_INVITES = 24
|
||||
CF_2000000 = 25
|
||||
CF_4000000 = 26
|
||||
CF_DEFENSE_SKILLS_AT_MAX_QUESTIONABLE = 27
|
||||
CF_SHOW_GUILD_HERALDRY = 28
|
||||
CF_SHOW_CLOAK = 29
|
||||
CF_IN_PVP = 30
|
||||
CF_IS_HATED = 31
|
||||
CF2_1 = 32
|
||||
CF2_2 = 33
|
||||
CF2_4 = 34
|
||||
CF2_ALLOW_LON_INVITES = 35
|
||||
CF2_SHOW_RANGED = 36
|
||||
CF2_ALLOW_VOICE_INVITES = 37
|
||||
CF2_CHARACTER_BONUS_EXPERIENCE_ENABLED = 38
|
||||
CF2_80 = 39
|
||||
CF2_100 = 40 // Hide achievements
|
||||
CF2_200 = 41
|
||||
CF2_400 = 42
|
||||
CF2_800 = 43 // Enable facebook updates
|
||||
CF2_1000 = 44 // Enable twitter updates
|
||||
CF2_2000 = 45 // Enable eq2 player updates
|
||||
CF2_4000 = 46 // EQ2 players, link to alt chars
|
||||
CF2_8000 = 47
|
||||
CF2_10000 = 48
|
||||
CF2_20000 = 49
|
||||
CF2_40000 = 50
|
||||
CF2_80000 = 51
|
||||
CF2_100000 = 52
|
||||
CF2_200000 = 53
|
||||
CF2_400000 = 54
|
||||
CF2_800000 = 55
|
||||
CF2_1000000 = 56
|
||||
CF2_2000000 = 57
|
||||
CF2_4000000 = 58
|
||||
CF2_8000000 = 59
|
||||
CF2_10000000 = 60
|
||||
CF2_20000000 = 61
|
||||
CF2_40000000 = 62
|
||||
CF2_80000000 = 63
|
||||
CF_MAXIMUM_FLAG = 63
|
||||
CF_HIDE_STATUS = 49 // For testing only
|
||||
CF_GM_HIDDEN = 50 // For testing only
|
||||
CF2_80 = 39
|
||||
CF2_100 = 40 // Hide achievements
|
||||
CF2_200 = 41
|
||||
CF2_400 = 42
|
||||
CF2_800 = 43 // Enable facebook updates
|
||||
CF2_1000 = 44 // Enable twitter updates
|
||||
CF2_2000 = 45 // Enable eq2 player updates
|
||||
CF2_4000 = 46 // EQ2 players, link to alt chars
|
||||
CF2_8000 = 47
|
||||
CF2_10000 = 48
|
||||
CF2_20000 = 49
|
||||
CF2_40000 = 50
|
||||
CF2_80000 = 51
|
||||
CF2_100000 = 52
|
||||
CF2_200000 = 53
|
||||
CF2_400000 = 54
|
||||
CF2_800000 = 55
|
||||
CF2_1000000 = 56
|
||||
CF2_2000000 = 57
|
||||
CF2_4000000 = 58
|
||||
CF2_8000000 = 59
|
||||
CF2_10000000 = 60
|
||||
CF2_20000000 = 61
|
||||
CF2_40000000 = 62
|
||||
CF2_80000000 = 63
|
||||
CF_MAXIMUM_FLAG = 63
|
||||
CF_HIDE_STATUS = 49 // For testing only
|
||||
CF_GM_HIDDEN = 50 // For testing only
|
||||
)
|
||||
|
||||
// Update activity constants
|
||||
const (
|
||||
UPDATE_ACTIVITY_FALLING = 0
|
||||
UPDATE_ACTIVITY_RUNNING = 128
|
||||
UPDATE_ACTIVITY_RIDING_BOAT = 256
|
||||
UPDATE_ACTIVITY_JUMPING = 1024
|
||||
UPDATE_ACTIVITY_IN_WATER_ABOVE = 6144
|
||||
UPDATE_ACTIVITY_IN_WATER_BELOW = 6272
|
||||
UPDATE_ACTIVITY_SITTING = 6336
|
||||
UPDATE_ACTIVITY_DROWNING = 14464
|
||||
UPDATE_ACTIVITY_DROWNING2 = 14336
|
||||
UPDATE_ACTIVITY_FALLING = 0
|
||||
UPDATE_ACTIVITY_RUNNING = 128
|
||||
UPDATE_ACTIVITY_RIDING_BOAT = 256
|
||||
UPDATE_ACTIVITY_JUMPING = 1024
|
||||
UPDATE_ACTIVITY_IN_WATER_ABOVE = 6144
|
||||
UPDATE_ACTIVITY_IN_WATER_BELOW = 6272
|
||||
UPDATE_ACTIVITY_SITTING = 6336
|
||||
UPDATE_ACTIVITY_DROWNING = 14464
|
||||
UPDATE_ACTIVITY_DROWNING2 = 14336
|
||||
|
||||
// Age of Malice (AOM) variants
|
||||
UPDATE_ACTIVITY_FALLING_AOM = 16384
|
||||
UPDATE_ACTIVITY_RIDING_BOAT_AOM = 256
|
||||
UPDATE_ACTIVITY_RUNNING_AOM = 16512
|
||||
UPDATE_ACTIVITY_JUMPING_AOM = 17408
|
||||
UPDATE_ACTIVITY_FALLING_AOM = 16384
|
||||
UPDATE_ACTIVITY_RIDING_BOAT_AOM = 256
|
||||
UPDATE_ACTIVITY_RUNNING_AOM = 16512
|
||||
UPDATE_ACTIVITY_JUMPING_AOM = 17408
|
||||
UPDATE_ACTIVITY_MOVE_WATER_BELOW_AOM = 22528
|
||||
UPDATE_ACTIVITY_MOVE_WATER_ABOVE_AOM = 22656
|
||||
UPDATE_ACTIVITY_SITTING_AOM = 22720
|
||||
UPDATE_ACTIVITY_DROWNING_AOM = 30720
|
||||
UPDATE_ACTIVITY_DROWNING2_AOM = 30848
|
||||
UPDATE_ACTIVITY_SITTING_AOM = 22720
|
||||
UPDATE_ACTIVITY_DROWNING_AOM = 30720
|
||||
UPDATE_ACTIVITY_DROWNING2_AOM = 30848
|
||||
)
|
||||
|
||||
// Effect slot constants
|
||||
@ -128,11 +128,11 @@ const (
|
||||
|
||||
// Quickbar type constants
|
||||
const (
|
||||
QUICKBAR_NORMAL = 1
|
||||
QUICKBAR_INV_SLOT = 2
|
||||
QUICKBAR_MACRO = 3
|
||||
QUICKBAR_TEXT_CMD = 4
|
||||
QUICKBAR_ITEM = 6
|
||||
QUICKBAR_NORMAL = 1
|
||||
QUICKBAR_INV_SLOT = 2
|
||||
QUICKBAR_MACRO = 3
|
||||
QUICKBAR_TEXT_CMD = 4
|
||||
QUICKBAR_ITEM = 6
|
||||
)
|
||||
|
||||
// Combat state constants
|
||||
@ -163,14 +163,14 @@ const (
|
||||
|
||||
// Add item type constants
|
||||
const (
|
||||
AddItemTypeNOT_SET = 0
|
||||
AddItemTypeQUEST = 1
|
||||
AddItemTypeBUY_FROM_MERCHANT = 2
|
||||
AddItemTypeLOOT = 3
|
||||
AddItemTypeTRADE = 4
|
||||
AddItemTypeMAIL = 5
|
||||
AddItemTypeHOUSE = 6
|
||||
AddItemTypeCRAFT = 7
|
||||
AddItemTypeCOLLECTION_REWARD = 8
|
||||
AddItemTypeNOT_SET = 0
|
||||
AddItemTypeQUEST = 1
|
||||
AddItemTypeBUY_FROM_MERCHANT = 2
|
||||
AddItemTypeLOOT = 3
|
||||
AddItemTypeTRADE = 4
|
||||
AddItemTypeMAIL = 5
|
||||
AddItemTypeHOUSE = 6
|
||||
AddItemTypeCRAFT = 7
|
||||
AddItemTypeCOLLECTION_REWARD = 8
|
||||
AddItemTypeTRADESKILL_ACHIEVEMENT = 9
|
||||
)
|
@ -1,7 +1,8 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"math"
|
||||
"eq2emu/internal/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetXPVitality returns the player's adventure XP vitality
|
||||
@ -141,7 +142,7 @@ func (p *Player) AddTSXP(xpAmount int32) bool {
|
||||
overflow := totalXP - neededXP
|
||||
|
||||
// Level up
|
||||
p.SetTSLevel(p.GetTSLevel()+1)
|
||||
p.SetTSLevel(p.GetTSLevel() + 1)
|
||||
p.SetNeededTSXPByLevel()
|
||||
|
||||
// Set XP to overflow amount
|
||||
|
@ -12,14 +12,14 @@ import (
|
||||
type Manager struct {
|
||||
// Players indexed by various keys
|
||||
playersLock sync.RWMutex
|
||||
players map[int32]*Player // playerID -> Player
|
||||
playersByName map[string]*Player // name -> Player (case insensitive)
|
||||
playersByCharID map[int32]*Player // characterID -> Player
|
||||
playersByZone map[int32][]*Player // zoneID -> []*Player
|
||||
players map[int32]*Player // playerID -> Player
|
||||
playersByName map[string]*Player // name -> Player (case insensitive)
|
||||
playersByCharID map[int32]*Player // characterID -> Player
|
||||
playersByZone map[int32][]*Player // zoneID -> []*Player
|
||||
|
||||
// Player statistics
|
||||
stats PlayerStats
|
||||
statsLock sync.RWMutex
|
||||
stats PlayerStats
|
||||
statsLock sync.RWMutex
|
||||
|
||||
// Event handlers
|
||||
eventHandlers []PlayerEventHandler
|
||||
|
@ -1,19 +1,11 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/common"
|
||||
"eq2emu/internal/entity"
|
||||
"eq2emu/internal/factions"
|
||||
"eq2emu/internal/languages"
|
||||
"eq2emu/internal/quests"
|
||||
"eq2emu/internal/skills"
|
||||
"eq2emu/internal/spells"
|
||||
"eq2emu/internal/titles"
|
||||
)
|
||||
|
||||
// Global XP table
|
||||
@ -32,37 +24,37 @@ func NewPlayer() *Player {
|
||||
houseVaultSlots: 0,
|
||||
|
||||
// Initialize maps
|
||||
playerQuests: make(map[int32]*quests.Quest),
|
||||
completedQuests: make(map[int32]*quests.Quest),
|
||||
pendingQuests: make(map[int32]*quests.Quest),
|
||||
currentQuestFlagged: make(map[*entity.Spawn]bool),
|
||||
playerSpawnQuestsRequired: make(map[int32][]int32),
|
||||
playerSpawnHistoryRequired: make(map[int32][]int32),
|
||||
spawnVisPacketList: make(map[int32]string),
|
||||
spawnInfoPacketList: make(map[int32]string),
|
||||
spawnPosPacketList: make(map[int32]string),
|
||||
spawnPacketSent: make(map[int32]int8),
|
||||
spawnStateList: make(map[int32]*SpawnQueueState),
|
||||
playerSpawnIDMap: make(map[int32]*entity.Spawn),
|
||||
playerSpawnReverseIDMap: make(map[*entity.Spawn]int32),
|
||||
playerAggroRangeSpawns: make(map[int32]bool),
|
||||
pendingLootItems: make(map[int32]map[int32]bool),
|
||||
friendList: make(map[string]int8),
|
||||
ignoreList: make(map[string]int8),
|
||||
characterHistory: make(map[int8]map[int8][]*HistoryData),
|
||||
charLuaHistory: make(map[int32]*LUAHistory),
|
||||
playersPoiList: make(map[int32][]int32),
|
||||
playerQuests: make(map[int32]*quests.Quest),
|
||||
completedQuests: make(map[int32]*quests.Quest),
|
||||
pendingQuests: make(map[int32]*quests.Quest),
|
||||
currentQuestFlagged: make(map[*entity.Spawn]bool),
|
||||
playerSpawnQuestsRequired: make(map[int32][]int32),
|
||||
playerSpawnHistoryRequired: make(map[int32][]int32),
|
||||
spawnVisPacketList: make(map[int32]string),
|
||||
spawnInfoPacketList: make(map[int32]string),
|
||||
spawnPosPacketList: make(map[int32]string),
|
||||
spawnPacketSent: make(map[int32]int8),
|
||||
spawnStateList: make(map[int32]*SpawnQueueState),
|
||||
playerSpawnIDMap: make(map[int32]*entity.Spawn),
|
||||
playerSpawnReverseIDMap: make(map[*entity.Spawn]int32),
|
||||
playerAggroRangeSpawns: make(map[int32]bool),
|
||||
pendingLootItems: make(map[int32]map[int32]bool),
|
||||
friendList: make(map[string]int8),
|
||||
ignoreList: make(map[string]int8),
|
||||
characterHistory: make(map[int8]map[int8][]*HistoryData),
|
||||
charLuaHistory: make(map[int32]*LUAHistory),
|
||||
playersPoiList: make(map[int32][]int32),
|
||||
pendingSelectableItemRewards: make(map[int32][]Item),
|
||||
statistics: make(map[int32]*Statistic),
|
||||
mailList: make(map[int32]*Mail),
|
||||
targetInvisHistory: make(map[int32]bool),
|
||||
spawnedBots: make(map[int32]int32),
|
||||
macroIcons: make(map[int32]int16),
|
||||
sortedTraitList: make(map[int8]map[int8][]*TraitData),
|
||||
classTraining: make(map[int8][]*TraitData),
|
||||
raceTraits: make(map[int8][]*TraitData),
|
||||
innateRaceTraits: make(map[int8][]*TraitData),
|
||||
focusEffects: make(map[int8][]*TraitData),
|
||||
statistics: make(map[int32]*Statistic),
|
||||
mailList: make(map[int32]*Mail),
|
||||
targetInvisHistory: make(map[int32]bool),
|
||||
spawnedBots: make(map[int32]int32),
|
||||
macroIcons: make(map[int32]int16),
|
||||
sortedTraitList: make(map[int8]map[int8][]*TraitData),
|
||||
classTraining: make(map[int8][]*TraitData),
|
||||
raceTraits: make(map[int8][]*TraitData),
|
||||
innateRaceTraits: make(map[int8][]*TraitData),
|
||||
focusEffects: make(map[int8][]*TraitData),
|
||||
}
|
||||
|
||||
// Initialize entity base
|
||||
|
@ -3,6 +3,7 @@ package player
|
||||
import (
|
||||
"eq2emu/internal/entity"
|
||||
"eq2emu/internal/quests"
|
||||
"eq2emu/internal/spells"
|
||||
)
|
||||
|
||||
// GetQuest returns a quest by ID
|
||||
|
@ -2,7 +2,6 @@ package player
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"eq2emu/internal/spells"
|
||||
)
|
||||
|
@ -30,12 +30,12 @@ const (
|
||||
|
||||
// HistoryData represents character history data matching the character_history table
|
||||
type HistoryData struct {
|
||||
Value int32
|
||||
Value2 int32
|
||||
Location [200]byte
|
||||
EventID int32
|
||||
EventDate int32
|
||||
NeedsSave bool
|
||||
Value int32
|
||||
Value2 int32
|
||||
Location [200]byte
|
||||
EventID int32
|
||||
EventDate int32
|
||||
NeedsSave bool
|
||||
}
|
||||
|
||||
// LUAHistory represents history set through the LUA system
|
||||
@ -110,15 +110,15 @@ type PlayerLoginAppearance struct {
|
||||
|
||||
// InstanceData represents instance information for a player
|
||||
type InstanceData struct {
|
||||
DBID int32
|
||||
InstanceID int32
|
||||
ZoneID int32
|
||||
ZoneInstanceType int8
|
||||
ZoneName string
|
||||
LastSuccessTimestamp int32
|
||||
LastFailureTimestamp int32
|
||||
SuccessLockoutTime int32
|
||||
FailureLockoutTime int32
|
||||
DBID int32
|
||||
InstanceID int32
|
||||
ZoneID int32
|
||||
ZoneInstanceType int8
|
||||
ZoneName string
|
||||
LastSuccessTimestamp int32
|
||||
LastFailureTimestamp int32
|
||||
SuccessLockoutTime int32
|
||||
FailureLockoutTime int32
|
||||
}
|
||||
|
||||
// CharacterInstances manages all instances for a character
|
||||
@ -129,31 +129,31 @@ type CharacterInstances struct {
|
||||
|
||||
// PlayerInfo contains detailed player information for serialization
|
||||
type PlayerInfo struct {
|
||||
player *Player
|
||||
infoStruct *entity.InfoStruct
|
||||
houseZoneID int32
|
||||
bindZoneID int32
|
||||
bindX float32
|
||||
bindY float32
|
||||
bindZ float32
|
||||
bindHeading float32
|
||||
boatXOffset float32
|
||||
boatYOffset float32
|
||||
boatZOffset float32
|
||||
boatSpawn int32
|
||||
changes []byte
|
||||
origPacket []byte
|
||||
petChanges []byte
|
||||
petOrigPacket []byte
|
||||
player *Player
|
||||
infoStruct *entity.InfoStruct
|
||||
houseZoneID int32
|
||||
bindZoneID int32
|
||||
bindX float32
|
||||
bindY float32
|
||||
bindZ float32
|
||||
bindHeading float32
|
||||
boatXOffset float32
|
||||
boatYOffset float32
|
||||
boatZOffset float32
|
||||
boatSpawn int32
|
||||
changes []byte
|
||||
origPacket []byte
|
||||
petChanges []byte
|
||||
petOrigPacket []byte
|
||||
}
|
||||
|
||||
// PlayerControlFlags manages player control flags
|
||||
type PlayerControlFlags struct {
|
||||
flagsChanged bool
|
||||
flagChanges map[int8]map[int8]int8
|
||||
currentFlags map[int8]map[int8]bool
|
||||
controlMutex sync.Mutex
|
||||
changesMutex sync.Mutex
|
||||
flagsChanged bool
|
||||
flagChanges map[int8]map[int8]int8
|
||||
currentFlags map[int8]map[int8]bool
|
||||
controlMutex sync.Mutex
|
||||
changesMutex sync.Mutex
|
||||
}
|
||||
|
||||
// PlayerGroup represents a player's group information
|
||||
@ -168,9 +168,9 @@ type GroupMemberInfo struct {
|
||||
|
||||
// Statistic represents a player statistic
|
||||
type Statistic struct {
|
||||
StatID int32
|
||||
Value int64
|
||||
Date int32
|
||||
StatID int32
|
||||
Value int64
|
||||
Date int32
|
||||
}
|
||||
|
||||
// Mail represents in-game mail
|
||||
@ -318,38 +318,38 @@ type Player struct {
|
||||
group *PlayerGroup
|
||||
|
||||
// Movement and position
|
||||
movementPacket []byte
|
||||
oldMovementPacket []byte
|
||||
movementPacket []byte
|
||||
oldMovementPacket []byte
|
||||
lastMovementActivity int16
|
||||
posPacketSpeed float32
|
||||
testX float32
|
||||
testY float32
|
||||
testZ float32
|
||||
testTime int32
|
||||
posPacketSpeed float32
|
||||
testX float32
|
||||
testY float32
|
||||
testZ float32
|
||||
testTime int32
|
||||
|
||||
// Combat
|
||||
rangeAttack bool
|
||||
combatTarget *entity.Entity
|
||||
resurrecting bool
|
||||
rangeAttack bool
|
||||
combatTarget *entity.Entity
|
||||
resurrecting bool
|
||||
|
||||
// Packet management
|
||||
packetNum int32
|
||||
spawnIndex int16
|
||||
spellCount int16
|
||||
packetNum int32
|
||||
spawnIndex int16
|
||||
spellCount int16
|
||||
spellOrigPacket []byte
|
||||
spellXorPacket []byte
|
||||
raidOrigPacket []byte
|
||||
raidXorPacket []byte
|
||||
|
||||
// Spawn management
|
||||
spawnVisPacketList map[int32]string
|
||||
spawnInfoPacketList map[int32]string
|
||||
spawnPosPacketList map[int32]string
|
||||
spawnPacketSent map[int32]int8
|
||||
spawnStateList map[int32]*SpawnQueueState
|
||||
playerSpawnIDMap map[int32]*entity.Spawn
|
||||
spawnVisPacketList map[int32]string
|
||||
spawnInfoPacketList map[int32]string
|
||||
spawnPosPacketList map[int32]string
|
||||
spawnPacketSent map[int32]int8
|
||||
spawnStateList map[int32]*SpawnQueueState
|
||||
playerSpawnIDMap map[int32]*entity.Spawn
|
||||
playerSpawnReverseIDMap map[*entity.Spawn]int32
|
||||
playerAggroRangeSpawns map[int32]bool
|
||||
playerAggroRangeSpawns map[int32]bool
|
||||
|
||||
// Temporary spawn packets for XOR
|
||||
spawnTmpVisXorPacket []byte
|
||||
@ -360,13 +360,13 @@ type Player struct {
|
||||
infoXorSize int32
|
||||
|
||||
// Packet structures
|
||||
spawnPosStruct *PacketStruct
|
||||
spawnInfoStruct *PacketStruct
|
||||
spawnVisStruct *PacketStruct
|
||||
spawnHeaderStruct *PacketStruct
|
||||
spawnFooterStruct *PacketStruct
|
||||
spawnPosStruct *PacketStruct
|
||||
spawnInfoStruct *PacketStruct
|
||||
spawnVisStruct *PacketStruct
|
||||
spawnHeaderStruct *PacketStruct
|
||||
spawnFooterStruct *PacketStruct
|
||||
widgetFooterStruct *PacketStruct
|
||||
signFooterStruct *PacketStruct
|
||||
signFooterStruct *PacketStruct
|
||||
|
||||
// Character flags
|
||||
charsheetChanged atomic.Bool
|
||||
@ -375,27 +375,27 @@ type Player struct {
|
||||
quickbarUpdated bool
|
||||
|
||||
// Quest system
|
||||
playerQuests map[int32]*quests.Quest
|
||||
completedQuests map[int32]*quests.Quest
|
||||
pendingQuests map[int32]*quests.Quest
|
||||
currentQuestFlagged map[*entity.Spawn]bool
|
||||
playerSpawnQuestsRequired map[int32][]int32
|
||||
playerSpawnHistoryRequired map[int32][]int32
|
||||
playerQuests map[int32]*quests.Quest
|
||||
completedQuests map[int32]*quests.Quest
|
||||
pendingQuests map[int32]*quests.Quest
|
||||
currentQuestFlagged map[*entity.Spawn]bool
|
||||
playerSpawnQuestsRequired map[int32][]int32
|
||||
playerSpawnHistoryRequired map[int32][]int32
|
||||
|
||||
// Skills and spells
|
||||
spells []*SpellBookEntry
|
||||
passiveSpells []int32
|
||||
skillList PlayerSkillList
|
||||
spells []*SpellBookEntry
|
||||
passiveSpells []int32
|
||||
skillList PlayerSkillList
|
||||
allSpellsLocked bool
|
||||
|
||||
// Items and equipment
|
||||
itemList PlayerItemList
|
||||
quickbarItems []*QuickBarItem
|
||||
itemList PlayerItemList
|
||||
quickbarItems []*QuickBarItem
|
||||
pendingLootItems map[int32]map[int32]bool
|
||||
|
||||
// Social lists
|
||||
friendList map[string]int8
|
||||
ignoreList map[string]int8
|
||||
friendList map[string]int8
|
||||
ignoreList map[string]int8
|
||||
|
||||
// Character history
|
||||
characterHistory map[int8]map[int8][]*HistoryData
|
||||
@ -405,12 +405,12 @@ type Player struct {
|
||||
playersPoiList map[int32][]int32
|
||||
|
||||
// Collections and achievements
|
||||
collectionList PlayerCollectionList
|
||||
pendingCollectionReward *Collection
|
||||
pendingItemRewards []Item
|
||||
collectionList PlayerCollectionList
|
||||
pendingCollectionReward *Collection
|
||||
pendingItemRewards []Item
|
||||
pendingSelectableItemRewards map[int32][]Item
|
||||
achievementList PlayerAchievementList
|
||||
achievementUpdateList PlayerAchievementUpdateList
|
||||
achievementList PlayerAchievementList
|
||||
achievementUpdateList PlayerAchievementUpdateList
|
||||
|
||||
// Titles and languages
|
||||
playerTitlesList PlayerTitlesList
|
||||
@ -435,17 +435,17 @@ type Player struct {
|
||||
characterInstances CharacterInstances
|
||||
|
||||
// Character state
|
||||
awayMessage string
|
||||
biography string
|
||||
isTracking bool
|
||||
pendingDeletion bool
|
||||
returningFromLD bool
|
||||
custNPC bool
|
||||
custNPCTarget *entity.Entity
|
||||
awayMessage string
|
||||
biography string
|
||||
isTracking bool
|
||||
pendingDeletion bool
|
||||
returningFromLD bool
|
||||
custNPC bool
|
||||
custNPCTarget *entity.Entity
|
||||
stopSaveSpellEffects bool
|
||||
gmVision bool
|
||||
resetMentorship bool
|
||||
activeReward bool
|
||||
gmVision bool
|
||||
resetMentorship bool
|
||||
activeReward bool
|
||||
|
||||
// Guild
|
||||
guild *Guild
|
||||
@ -482,31 +482,31 @@ type Player struct {
|
||||
houseVaultSlots int8
|
||||
|
||||
// Traits
|
||||
sortedTraitList map[int8]map[int8][]*TraitData
|
||||
classTraining map[int8][]*TraitData
|
||||
raceTraits map[int8][]*TraitData
|
||||
innateRaceTraits map[int8][]*TraitData
|
||||
focusEffects map[int8][]*TraitData
|
||||
needTraitUpdate atomic.Bool
|
||||
sortedTraitList map[int8]map[int8][]*TraitData
|
||||
classTraining map[int8][]*TraitData
|
||||
raceTraits map[int8][]*TraitData
|
||||
innateRaceTraits map[int8][]*TraitData
|
||||
focusEffects map[int8][]*TraitData
|
||||
needTraitUpdate atomic.Bool
|
||||
|
||||
// Mutexes
|
||||
playerQuestsMutex sync.RWMutex
|
||||
spellsBookMutex sync.RWMutex
|
||||
recipeBookMutex sync.RWMutex
|
||||
playerSpawnQuestsRequiredMutex sync.RWMutex
|
||||
playerQuestsMutex sync.RWMutex
|
||||
spellsBookMutex sync.RWMutex
|
||||
recipeBookMutex sync.RWMutex
|
||||
playerSpawnQuestsRequiredMutex sync.RWMutex
|
||||
playerSpawnHistoryRequiredMutex sync.RWMutex
|
||||
luaHistoryMutex sync.RWMutex
|
||||
controlFlagsMutex sync.RWMutex
|
||||
infoMutex sync.RWMutex
|
||||
posMutex sync.RWMutex
|
||||
visMutex sync.RWMutex
|
||||
indexMutex sync.RWMutex
|
||||
spawnMutex sync.RWMutex
|
||||
spawnAggroRangeMutex sync.RWMutex
|
||||
traitMutex sync.RWMutex
|
||||
spellPacketUpdateMutex sync.RWMutex
|
||||
raidUpdateMutex sync.RWMutex
|
||||
mailMutex sync.RWMutex
|
||||
luaHistoryMutex sync.RWMutex
|
||||
controlFlagsMutex sync.RWMutex
|
||||
infoMutex sync.RWMutex
|
||||
posMutex sync.RWMutex
|
||||
visMutex sync.RWMutex
|
||||
indexMutex sync.RWMutex
|
||||
spawnMutex sync.RWMutex
|
||||
spawnAggroRangeMutex sync.RWMutex
|
||||
traitMutex sync.RWMutex
|
||||
spellPacketUpdateMutex sync.RWMutex
|
||||
raidUpdateMutex sync.RWMutex
|
||||
mailMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// SkillBonus represents a skill bonus from a spell
|
||||
|
@ -1,5 +1,10 @@
|
||||
package quests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Action management methods for Quest
|
||||
|
||||
// AddCompleteAction adds a completion action for a step
|
||||
@ -339,7 +344,7 @@ func (q *Quest) validateLuaSyntax(luaCode string) error {
|
||||
|
||||
// Check for obviously invalid syntax patterns
|
||||
invalidPatterns := []string{
|
||||
"--[[", // Unfinished multi-line comments
|
||||
"--[[", // Unfinished multi-line comments
|
||||
"function(", // Function without closing
|
||||
}
|
||||
|
||||
|
@ -2,28 +2,28 @@ package quests
|
||||
|
||||
// Quest step type constants
|
||||
const (
|
||||
StepTypeKill int8 = 1
|
||||
StepTypeChat int8 = 2
|
||||
StepTypeObtainItem int8 = 3
|
||||
StepTypeLocation int8 = 4
|
||||
StepTypeSpell int8 = 5
|
||||
StepTypeNormal int8 = 6
|
||||
StepTypeCraft int8 = 7
|
||||
StepTypeHarvest int8 = 8
|
||||
StepTypeKillRaceReq int8 = 9 // kill using race type requirement instead of npc db id
|
||||
StepTypeKill int8 = 1
|
||||
StepTypeChat int8 = 2
|
||||
StepTypeObtainItem int8 = 3
|
||||
StepTypeLocation int8 = 4
|
||||
StepTypeSpell int8 = 5
|
||||
StepTypeNormal int8 = 6
|
||||
StepTypeCraft int8 = 7
|
||||
StepTypeHarvest int8 = 8
|
||||
StepTypeKillRaceReq int8 = 9 // kill using race type requirement instead of npc db id
|
||||
)
|
||||
|
||||
// Quest display status constants
|
||||
const (
|
||||
DisplayStatusHidden int32 = 0
|
||||
DisplayStatusNoCheck int32 = 1
|
||||
DisplayStatusYellow int32 = 2
|
||||
DisplayStatusCompleted int32 = 4
|
||||
DisplayStatusRepeatable int32 = 8
|
||||
DisplayStatusCanShare int32 = 16
|
||||
DisplayStatusHidden int32 = 0
|
||||
DisplayStatusNoCheck int32 = 1
|
||||
DisplayStatusYellow int32 = 2
|
||||
DisplayStatusCompleted int32 = 4
|
||||
DisplayStatusRepeatable int32 = 8
|
||||
DisplayStatusCanShare int32 = 16
|
||||
DisplayStatusCompleteFlag int32 = 32
|
||||
DisplayStatusShow int32 = 64
|
||||
DisplayStatusCheck int32 = 128
|
||||
DisplayStatusShow int32 = 64
|
||||
DisplayStatusCheck int32 = 128
|
||||
)
|
||||
|
||||
// Quest shareable flags
|
||||
|
@ -1,5 +1,7 @@
|
||||
package quests
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Player interface defines the required player functionality for quest system
|
||||
type Player interface {
|
||||
// Basic player information
|
||||
@ -155,11 +157,11 @@ type QuestRewardProcessor interface {
|
||||
|
||||
// QuestRewards contains calculated quest rewards
|
||||
type QuestRewards struct {
|
||||
Coins int64 `json:"coins"`
|
||||
Experience int32 `json:"experience"`
|
||||
TSExperience int32 `json:"ts_experience"`
|
||||
StatusPoints int32 `json:"status_points"`
|
||||
Items []Item `json:"items"`
|
||||
Coins int64 `json:"coins"`
|
||||
Experience int32 `json:"experience"`
|
||||
TSExperience int32 `json:"ts_experience"`
|
||||
StatusPoints int32 `json:"status_points"`
|
||||
Items []Item `json:"items"`
|
||||
FactionRewards map[int32]int32 `json:"faction_rewards"`
|
||||
}
|
||||
|
||||
|
@ -246,11 +246,11 @@ func (mql *MasterQuestList) GetQuestStatistics() *QuestStatistics {
|
||||
|
||||
// QuestStatistics contains statistical information about quests
|
||||
type QuestStatistics struct {
|
||||
TotalQuests int `json:"total_quests"`
|
||||
QuestsByType map[string]int `json:"quests_by_type"`
|
||||
QuestsByLevel map[int8]int `json:"quests_by_level"`
|
||||
RepeatableCount int `json:"repeatable_count"`
|
||||
HiddenCount int `json:"hidden_count"`
|
||||
TotalQuests int `json:"total_quests"`
|
||||
QuestsByType map[string]int `json:"quests_by_type"`
|
||||
QuestsByLevel map[int8]int `json:"quests_by_level"`
|
||||
RepeatableCount int `json:"repeatable_count"`
|
||||
HiddenCount int `json:"hidden_count"`
|
||||
}
|
||||
|
||||
// QuestManager provides high-level quest management functionality
|
||||
@ -443,8 +443,8 @@ func (qm *QuestManager) GetPlayerQuestStatistics(playerID int32) *PlayerQuestSta
|
||||
|
||||
// PlayerQuestStatistics contains statistical information about a player's quests
|
||||
type PlayerQuestStatistics struct {
|
||||
TotalQuests int `json:"total_quests"`
|
||||
CompletedQuests int `json:"completed_quests"`
|
||||
QuestsByType map[string]int `json:"quests_by_type"`
|
||||
QuestsByLevel map[int8]int `json:"quests_by_level"`
|
||||
TotalQuests int `json:"total_quests"`
|
||||
CompletedQuests int `json:"completed_quests"`
|
||||
QuestsByType map[string]int `json:"quests_by_type"`
|
||||
QuestsByLevel map[int8]int `json:"quests_by_level"`
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package quests
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Prerequisite management methods for Quest
|
||||
|
||||
// SetPrereqLevel sets the minimum level requirement
|
||||
|
@ -1,5 +1,7 @@
|
||||
package quests
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Reward management methods for Quest
|
||||
|
||||
// AddRewardCoins adds coin rewards
|
||||
|
@ -3,16 +3,15 @@ package quests
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
"eq2emu/internal/common"
|
||||
)
|
||||
|
||||
// Location represents a 3D location in a zone for quest steps
|
||||
type Location struct {
|
||||
ID int32 `json:"id"`
|
||||
X float32 `json:"x"`
|
||||
Y float32 `json:"y"`
|
||||
Z float32 `json:"z"`
|
||||
ZoneID int32 `json:"zone_id"`
|
||||
ID int32 `json:"id"`
|
||||
X float32 `json:"x"`
|
||||
Y float32 `json:"y"`
|
||||
Z float32 `json:"z"`
|
||||
ZoneID int32 `json:"zone_id"`
|
||||
}
|
||||
|
||||
// NewLocation creates a new location
|
||||
@ -45,25 +44,25 @@ func NewQuestFactionPrereq(factionID, min, max int32) *QuestFactionPrereq {
|
||||
// QuestStep represents a single step in a quest
|
||||
type QuestStep struct {
|
||||
// Basic step data
|
||||
ID int32 `json:"step_id"`
|
||||
Type int8 `json:"type"`
|
||||
Description string `json:"description"`
|
||||
TaskGroup string `json:"task_group"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
StepProgress int32 `json:"step_progress"`
|
||||
Icon int16 `json:"icon"`
|
||||
MaxVariation float32 `json:"max_variation"`
|
||||
Percentage float32 `json:"percentage"`
|
||||
UsableItemID int32 `json:"usable_item_id"`
|
||||
ID int32 `json:"step_id"`
|
||||
Type int8 `json:"type"`
|
||||
Description string `json:"description"`
|
||||
TaskGroup string `json:"task_group"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
StepProgress int32 `json:"step_progress"`
|
||||
Icon int16 `json:"icon"`
|
||||
MaxVariation float32 `json:"max_variation"`
|
||||
Percentage float32 `json:"percentage"`
|
||||
UsableItemID int32 `json:"usable_item_id"`
|
||||
|
||||
// Tracking data
|
||||
UpdateName string `json:"update_name"`
|
||||
UpdateTargetName string `json:"update_target_name"`
|
||||
Updated bool `json:"updated"`
|
||||
UpdateName string `json:"update_name"`
|
||||
UpdateTargetName string `json:"update_target_name"`
|
||||
Updated bool `json:"updated"`
|
||||
|
||||
// Step data (one of these will be populated based on type)
|
||||
IDs map[int32]bool `json:"ids,omitempty"` // For kill, chat, obtain item, etc.
|
||||
Locations []*Location `json:"locations,omitempty"` // For location steps
|
||||
IDs map[int32]bool `json:"ids,omitempty"` // For kill, chat, obtain item, etc.
|
||||
Locations []*Location `json:"locations,omitempty"` // For location steps
|
||||
|
||||
// Thread safety
|
||||
mutex sync.RWMutex
|
||||
@ -323,100 +322,100 @@ func (qs *QuestStep) SetIcon(icon int16) {
|
||||
// Quest represents a complete quest with all its steps and requirements
|
||||
type Quest struct {
|
||||
// Basic quest information
|
||||
ID int32 `json:"quest_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Zone string `json:"zone"`
|
||||
Level int8 `json:"level"`
|
||||
EncounterLevel int8 `json:"encounter_level"`
|
||||
Description string `json:"description"`
|
||||
CompletedDesc string `json:"completed_description"`
|
||||
ID int32 `json:"quest_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Zone string `json:"zone"`
|
||||
Level int8 `json:"level"`
|
||||
EncounterLevel int8 `json:"encounter_level"`
|
||||
Description string `json:"description"`
|
||||
CompletedDesc string `json:"completed_description"`
|
||||
|
||||
// Quest giver and return NPC
|
||||
QuestGiver int32 `json:"quest_giver"`
|
||||
ReturnID int32 `json:"return_id"`
|
||||
QuestGiver int32 `json:"quest_giver"`
|
||||
ReturnID int32 `json:"return_id"`
|
||||
|
||||
// Prerequisites
|
||||
PrereqLevel int8 `json:"prereq_level"`
|
||||
PrereqTSLevel int8 `json:"prereq_ts_level"`
|
||||
PrereqMaxLevel int8 `json:"prereq_max_level"`
|
||||
PrereqMaxTSLevel int8 `json:"prereq_max_ts_level"`
|
||||
PrereqFactions []*QuestFactionPrereq `json:"prereq_factions"`
|
||||
PrereqRaces []int8 `json:"prereq_races"`
|
||||
PrereqModelTypes []int16 `json:"prereq_model_types"`
|
||||
PrereqClasses []int8 `json:"prereq_classes"`
|
||||
PrereqTSClasses []int8 `json:"prereq_ts_classes"`
|
||||
PrereqQuests []int32 `json:"prereq_quests"`
|
||||
PrereqLevel int8 `json:"prereq_level"`
|
||||
PrereqTSLevel int8 `json:"prereq_ts_level"`
|
||||
PrereqMaxLevel int8 `json:"prereq_max_level"`
|
||||
PrereqMaxTSLevel int8 `json:"prereq_max_ts_level"`
|
||||
PrereqFactions []*QuestFactionPrereq `json:"prereq_factions"`
|
||||
PrereqRaces []int8 `json:"prereq_races"`
|
||||
PrereqModelTypes []int16 `json:"prereq_model_types"`
|
||||
PrereqClasses []int8 `json:"prereq_classes"`
|
||||
PrereqTSClasses []int8 `json:"prereq_ts_classes"`
|
||||
PrereqQuests []int32 `json:"prereq_quests"`
|
||||
|
||||
// Rewards
|
||||
RewardCoins int64 `json:"reward_coins"`
|
||||
RewardCoinsMax int64 `json:"reward_coins_max"`
|
||||
RewardFactions map[int32]int32 `json:"reward_factions"`
|
||||
RewardStatus int32 `json:"reward_status"`
|
||||
RewardComment string `json:"reward_comment"`
|
||||
RewardExp int32 `json:"reward_exp"`
|
||||
RewardTSExp int32 `json:"reward_ts_exp"`
|
||||
GeneratedCoin int64 `json:"generated_coin"`
|
||||
RewardCoins int64 `json:"reward_coins"`
|
||||
RewardCoinsMax int64 `json:"reward_coins_max"`
|
||||
RewardFactions map[int32]int32 `json:"reward_factions"`
|
||||
RewardStatus int32 `json:"reward_status"`
|
||||
RewardComment string `json:"reward_comment"`
|
||||
RewardExp int32 `json:"reward_exp"`
|
||||
RewardTSExp int32 `json:"reward_ts_exp"`
|
||||
GeneratedCoin int64 `json:"generated_coin"`
|
||||
|
||||
// Temporary rewards
|
||||
TmpRewardStatus int32 `json:"tmp_reward_status"`
|
||||
TmpRewardCoins int64 `json:"tmp_reward_coins"`
|
||||
TmpRewardStatus int32 `json:"tmp_reward_status"`
|
||||
TmpRewardCoins int64 `json:"tmp_reward_coins"`
|
||||
|
||||
// Steps and task groups
|
||||
QuestSteps []*QuestStep `json:"quest_steps"`
|
||||
QuestStepMap map[int32]*QuestStep `json:"-"` // For quick lookup
|
||||
QuestStepReverseMap map[*QuestStep]int32 `json:"-"` // Reverse lookup
|
||||
StepUpdates []*QuestStep `json:"-"` // Steps that were updated
|
||||
StepFailures []*QuestStep `json:"-"` // Steps that failed
|
||||
TaskGroupOrder map[int16]string `json:"task_group_order"`
|
||||
TaskGroup map[string][]*QuestStep `json:"-"` // Grouped steps
|
||||
TaskGroupNum int16 `json:"task_group_num"`
|
||||
QuestSteps []*QuestStep `json:"quest_steps"`
|
||||
QuestStepMap map[int32]*QuestStep `json:"-"` // For quick lookup
|
||||
QuestStepReverseMap map[*QuestStep]int32 `json:"-"` // Reverse lookup
|
||||
StepUpdates []*QuestStep `json:"-"` // Steps that were updated
|
||||
StepFailures []*QuestStep `json:"-"` // Steps that failed
|
||||
TaskGroupOrder map[int16]string `json:"task_group_order"`
|
||||
TaskGroup map[string][]*QuestStep `json:"-"` // Grouped steps
|
||||
TaskGroupNum int16 `json:"task_group_num"`
|
||||
|
||||
// Actions
|
||||
CompleteActions map[int32]string `json:"complete_actions"`
|
||||
ProgressActions map[int32]string `json:"progress_actions"`
|
||||
FailedActions map[int32]string `json:"failed_actions"`
|
||||
CompleteAction string `json:"complete_action"`
|
||||
CompleteActions map[int32]string `json:"complete_actions"`
|
||||
ProgressActions map[int32]string `json:"progress_actions"`
|
||||
FailedActions map[int32]string `json:"failed_actions"`
|
||||
CompleteAction string `json:"complete_action"`
|
||||
|
||||
// State tracking
|
||||
Deleted bool `json:"deleted"`
|
||||
TurnedIn bool `json:"turned_in"`
|
||||
UpdateNeeded bool `json:"update_needed"`
|
||||
HasSentLastUpdate bool `json:"has_sent_last_update"`
|
||||
NeedsSave bool `json:"needs_save"`
|
||||
Visible int8 `json:"visible"`
|
||||
Deleted bool `json:"deleted"`
|
||||
TurnedIn bool `json:"turned_in"`
|
||||
UpdateNeeded bool `json:"update_needed"`
|
||||
HasSentLastUpdate bool `json:"has_sent_last_update"`
|
||||
NeedsSave bool `json:"needs_save"`
|
||||
Visible int8 `json:"visible"`
|
||||
|
||||
// Date tracking
|
||||
Day int8 `json:"day"`
|
||||
Month int8 `json:"month"`
|
||||
Year int8 `json:"year"`
|
||||
Day int8 `json:"day"`
|
||||
Month int8 `json:"month"`
|
||||
Year int8 `json:"year"`
|
||||
|
||||
// Quest flags and settings
|
||||
FeatherColor int8 `json:"feather_color"`
|
||||
Repeatable bool `json:"repeatable"`
|
||||
Tracked bool `json:"tracked"`
|
||||
CompletedFlag bool `json:"completed_flag"`
|
||||
YellowName bool `json:"yellow_name"`
|
||||
QuestFlags int32 `json:"quest_flags"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Status int32 `json:"status"`
|
||||
FeatherColor int8 `json:"feather_color"`
|
||||
Repeatable bool `json:"repeatable"`
|
||||
Tracked bool `json:"tracked"`
|
||||
CompletedFlag bool `json:"completed_flag"`
|
||||
YellowName bool `json:"yellow_name"`
|
||||
QuestFlags int32 `json:"quest_flags"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Status int32 `json:"status"`
|
||||
|
||||
// Timer and completion tracking
|
||||
Timestamp int32 `json:"timestamp"`
|
||||
TimerStep int32 `json:"timer_step"`
|
||||
CompleteCount int16 `json:"complete_count"`
|
||||
Timestamp int32 `json:"timestamp"`
|
||||
TimerStep int32 `json:"timer_step"`
|
||||
CompleteCount int16 `json:"complete_count"`
|
||||
|
||||
// Temporary state
|
||||
QuestStateTemporary bool `json:"quest_state_temporary"`
|
||||
QuestTempDescription string `json:"quest_temp_description"`
|
||||
QuestShareableFlag int32 `json:"quest_shareable_flag"`
|
||||
CanDeleteQuest bool `json:"can_delete_quest"`
|
||||
StatusToEarnMin int32 `json:"status_to_earn_min"`
|
||||
StatusToEarnMax int32 `json:"status_to_earn_max"`
|
||||
HideReward bool `json:"hide_reward"`
|
||||
QuestStateTemporary bool `json:"quest_state_temporary"`
|
||||
QuestTempDescription string `json:"quest_temp_description"`
|
||||
QuestShareableFlag int32 `json:"quest_shareable_flag"`
|
||||
CanDeleteQuest bool `json:"can_delete_quest"`
|
||||
StatusToEarnMin int32 `json:"status_to_earn_min"`
|
||||
StatusToEarnMax int32 `json:"status_to_earn_max"`
|
||||
HideReward bool `json:"hide_reward"`
|
||||
|
||||
// Thread safety
|
||||
stepsMutex sync.RWMutex
|
||||
stepsMutex sync.RWMutex
|
||||
completeActionsMutex sync.RWMutex
|
||||
progressActionsMutex sync.RWMutex
|
||||
failedActionsMutex sync.RWMutex
|
||||
@ -427,46 +426,46 @@ func NewQuest(id int32) *Quest {
|
||||
now := time.Now()
|
||||
|
||||
quest := &Quest{
|
||||
ID: id,
|
||||
PrereqLevel: DefaultPrereqLevel,
|
||||
PrereqTSLevel: 0,
|
||||
PrereqMaxLevel: 0,
|
||||
PrereqMaxTSLevel: 0,
|
||||
RewardCoins: 0,
|
||||
RewardCoinsMax: 0,
|
||||
CompletedFlag: false,
|
||||
HasSentLastUpdate: false,
|
||||
EncounterLevel: 0,
|
||||
RewardExp: 0,
|
||||
RewardTSExp: 0,
|
||||
FeatherColor: 0,
|
||||
Repeatable: false,
|
||||
YellowName: false,
|
||||
Hidden: false,
|
||||
GeneratedCoin: 0,
|
||||
QuestFlags: 0,
|
||||
Timestamp: 0,
|
||||
CompleteCount: 0,
|
||||
QuestStateTemporary: false,
|
||||
TmpRewardStatus: 0,
|
||||
TmpRewardCoins: 0,
|
||||
CompletedDesc: "",
|
||||
ID: id,
|
||||
PrereqLevel: DefaultPrereqLevel,
|
||||
PrereqTSLevel: 0,
|
||||
PrereqMaxLevel: 0,
|
||||
PrereqMaxTSLevel: 0,
|
||||
RewardCoins: 0,
|
||||
RewardCoinsMax: 0,
|
||||
CompletedFlag: false,
|
||||
HasSentLastUpdate: false,
|
||||
EncounterLevel: 0,
|
||||
RewardExp: 0,
|
||||
RewardTSExp: 0,
|
||||
FeatherColor: 0,
|
||||
Repeatable: false,
|
||||
YellowName: false,
|
||||
Hidden: false,
|
||||
GeneratedCoin: 0,
|
||||
QuestFlags: 0,
|
||||
Timestamp: 0,
|
||||
CompleteCount: 0,
|
||||
QuestStateTemporary: false,
|
||||
TmpRewardStatus: 0,
|
||||
TmpRewardCoins: 0,
|
||||
CompletedDesc: "",
|
||||
QuestTempDescription: "",
|
||||
QuestShareableFlag: 0,
|
||||
CanDeleteQuest: false,
|
||||
Status: 0,
|
||||
StatusToEarnMin: 0,
|
||||
StatusToEarnMax: 0,
|
||||
HideReward: false,
|
||||
Deleted: false,
|
||||
TurnedIn: false,
|
||||
UpdateNeeded: true,
|
||||
NeedsSave: false,
|
||||
TaskGroupNum: DefaultTaskGroupNum,
|
||||
Visible: DefaultVisible,
|
||||
Day: int8(now.Day()),
|
||||
Month: int8(now.Month()),
|
||||
Year: int8(now.Year() - 2000), // EQ2 uses 2-digit years
|
||||
QuestShareableFlag: 0,
|
||||
CanDeleteQuest: false,
|
||||
Status: 0,
|
||||
StatusToEarnMin: 0,
|
||||
StatusToEarnMax: 0,
|
||||
HideReward: false,
|
||||
Deleted: false,
|
||||
TurnedIn: false,
|
||||
UpdateNeeded: true,
|
||||
NeedsSave: false,
|
||||
TaskGroupNum: DefaultTaskGroupNum,
|
||||
Visible: DefaultVisible,
|
||||
Day: int8(now.Day()),
|
||||
Month: int8(now.Month()),
|
||||
Year: int8(now.Year() - 2000), // EQ2 uses 2-digit years
|
||||
|
||||
// Initialize maps and slices
|
||||
QuestStepMap: make(map[int32]*QuestStep),
|
||||
|
@ -88,39 +88,39 @@ func (r *Races) initializeRaces() {
|
||||
// Initialize good races (from C++ race_map_good)
|
||||
// "Neutral" races appear in both lists for /randomize functionality
|
||||
r.goodRaces = []string{
|
||||
RaceNameDwarf, // 0
|
||||
RaceNameFaeLight, // 1
|
||||
RaceNameFroglok, // 2
|
||||
RaceNameHalfling, // 3
|
||||
RaceNameHighElf, // 4
|
||||
RaceNameWoodElf, // 5
|
||||
RaceNameBarbarian, // 6 (neutral)
|
||||
RaceNameErudite, // 7 (neutral)
|
||||
RaceNameGnome, // 8 (neutral)
|
||||
RaceNameHalfElf, // 9 (neutral)
|
||||
RaceNameHuman, // 10 (neutral)
|
||||
RaceNameKerra, // 11 (neutral)
|
||||
RaceNameVampire, // 12 (neutral)
|
||||
RaceNameAerakyn, // 13 (neutral)
|
||||
RaceNameDwarf, // 0
|
||||
RaceNameFaeLight, // 1
|
||||
RaceNameFroglok, // 2
|
||||
RaceNameHalfling, // 3
|
||||
RaceNameHighElf, // 4
|
||||
RaceNameWoodElf, // 5
|
||||
RaceNameBarbarian, // 6 (neutral)
|
||||
RaceNameErudite, // 7 (neutral)
|
||||
RaceNameGnome, // 8 (neutral)
|
||||
RaceNameHalfElf, // 9 (neutral)
|
||||
RaceNameHuman, // 10 (neutral)
|
||||
RaceNameKerra, // 11 (neutral)
|
||||
RaceNameVampire, // 12 (neutral)
|
||||
RaceNameAerakyn, // 13 (neutral)
|
||||
}
|
||||
|
||||
// Initialize evil races (from C++ race_map_evil)
|
||||
r.evilRaces = []string{
|
||||
RaceNameFaeDark, // 0
|
||||
RaceNameDarkElf, // 1
|
||||
RaceNameIksar, // 2
|
||||
RaceNameOgre, // 3
|
||||
RaceNameRatonga, // 4
|
||||
RaceNameSarnak, // 5
|
||||
RaceNameTroll, // 6
|
||||
RaceNameBarbarian, // 7 (neutral)
|
||||
RaceNameErudite, // 8 (neutral)
|
||||
RaceNameGnome, // 9 (neutral)
|
||||
RaceNameHalfElf, // 10 (neutral)
|
||||
RaceNameHuman, // 11 (neutral)
|
||||
RaceNameKerra, // 12 (neutral)
|
||||
RaceNameVampire, // 13 (neutral)
|
||||
RaceNameAerakyn, // 14 (neutral)
|
||||
RaceNameFaeDark, // 0
|
||||
RaceNameDarkElf, // 1
|
||||
RaceNameIksar, // 2
|
||||
RaceNameOgre, // 3
|
||||
RaceNameRatonga, // 4
|
||||
RaceNameSarnak, // 5
|
||||
RaceNameTroll, // 6
|
||||
RaceNameBarbarian, // 7 (neutral)
|
||||
RaceNameErudite, // 8 (neutral)
|
||||
RaceNameGnome, // 9 (neutral)
|
||||
RaceNameHalfElf, // 10 (neutral)
|
||||
RaceNameHuman, // 11 (neutral)
|
||||
RaceNameKerra, // 12 (neutral)
|
||||
RaceNameVampire, // 13 (neutral)
|
||||
RaceNameAerakyn, // 14 (neutral)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package races
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
@ -37,24 +37,24 @@ const (
|
||||
// Tradeskill class bitmasks
|
||||
// These are used to determine which classes can use a recipe
|
||||
const (
|
||||
TradeskillAny = 3 // 1+2: Any class can use
|
||||
TradeskillAdornment = 1 // Adornment recipes
|
||||
TradeskillArtisan = 2 // Base artisan recipes
|
||||
TradeskillAny = 3 // 1+2: Any class can use
|
||||
TradeskillAdornment = 1 // Adornment recipes
|
||||
TradeskillArtisan = 2 // Base artisan recipes
|
||||
|
||||
// Base tradeskill classes (bits 0-12)
|
||||
TradeskillProvisioner = 1 << 0
|
||||
TradeskillWoodworker = 1 << 1
|
||||
TradeskillCarpenter = 1 << 2
|
||||
TradeskillOutfitter = 1 << 3
|
||||
TradeskillArmorer = 1 << 4
|
||||
TradeskillWeaponsmith = 1 << 5
|
||||
TradeskillTailor = 1 << 6
|
||||
TradeskillScholar = 1 << 7
|
||||
TradeskillJeweler = 1 << 8
|
||||
TradeskillSage = 1 << 9
|
||||
TradeskillAlchemist = 1 << 10
|
||||
TradeskillCraftsman = 1 << 11
|
||||
TradeskillTinkerer = 1 << 12
|
||||
TradeskillProvisioner = 1 << 0
|
||||
TradeskillWoodworker = 1 << 1
|
||||
TradeskillCarpenter = 1 << 2
|
||||
TradeskillOutfitter = 1 << 3
|
||||
TradeskillArmorer = 1 << 4
|
||||
TradeskillWeaponsmith = 1 << 5
|
||||
TradeskillTailor = 1 << 6
|
||||
TradeskillScholar = 1 << 7
|
||||
TradeskillJeweler = 1 << 8
|
||||
TradeskillSage = 1 << 9
|
||||
TradeskillAlchemist = 1 << 10
|
||||
TradeskillCraftsman = 1 << 11
|
||||
TradeskillTinkerer = 1 << 12
|
||||
)
|
||||
|
||||
// Crafting device types
|
||||
@ -90,30 +90,30 @@ const (
|
||||
|
||||
// Error variables
|
||||
var (
|
||||
ErrRecipeNotFound = errors.New("recipe not found")
|
||||
ErrRecipeBookNotFound = errors.New("recipe book not found")
|
||||
ErrInvalidRecipeID = errors.New("invalid recipe ID")
|
||||
ErrInvalidRecipeData = errors.New("invalid recipe data")
|
||||
ErrDuplicateRecipe = errors.New("duplicate recipe ID")
|
||||
ErrDuplicateRecipeBook = errors.New("duplicate recipe book ID")
|
||||
ErrMissingComponents = errors.New("missing required components")
|
||||
ErrInsufficientSkill = errors.New("insufficient skill level")
|
||||
ErrWrongTradeskillClass = errors.New("wrong tradeskill class")
|
||||
ErrWrongDevice = errors.New("wrong crafting device")
|
||||
ErrCannotLearnRecipe = errors.New("cannot learn recipe")
|
||||
ErrCannotUseRecipeBook = errors.New("cannot use recipe book")
|
||||
ErrCraftingInProgress = errors.New("crafting session in progress")
|
||||
ErrInvalidCraftingStage = errors.New("invalid crafting stage")
|
||||
ErrRecipeNotFound = errors.New("recipe not found")
|
||||
ErrRecipeBookNotFound = errors.New("recipe book not found")
|
||||
ErrInvalidRecipeID = errors.New("invalid recipe ID")
|
||||
ErrInvalidRecipeData = errors.New("invalid recipe data")
|
||||
ErrDuplicateRecipe = errors.New("duplicate recipe ID")
|
||||
ErrDuplicateRecipeBook = errors.New("duplicate recipe book ID")
|
||||
ErrMissingComponents = errors.New("missing required components")
|
||||
ErrInsufficientSkill = errors.New("insufficient skill level")
|
||||
ErrWrongTradeskillClass = errors.New("wrong tradeskill class")
|
||||
ErrWrongDevice = errors.New("wrong crafting device")
|
||||
ErrCannotLearnRecipe = errors.New("cannot learn recipe")
|
||||
ErrCannotUseRecipeBook = errors.New("cannot use recipe book")
|
||||
ErrCraftingInProgress = errors.New("crafting session in progress")
|
||||
ErrInvalidCraftingStage = errors.New("invalid crafting stage")
|
||||
ErrCraftingSessionNotFound = errors.New("crafting session not found")
|
||||
)
|
||||
|
||||
// Database table and column names
|
||||
const (
|
||||
TableRecipes = "recipe"
|
||||
TableRecipeComponents = "recipe_comp_list_item"
|
||||
TableRecipeSecondaryComp = "recipe_secondary_comp"
|
||||
TableCharacterRecipes = "character_recipes"
|
||||
TableRecipes = "recipe"
|
||||
TableRecipeComponents = "recipe_comp_list_item"
|
||||
TableRecipeSecondaryComp = "recipe_secondary_comp"
|
||||
TableCharacterRecipes = "character_recipes"
|
||||
TableCharacterRecipeBooks = "character_recipe_books"
|
||||
TableItems = "items"
|
||||
TableItemDetailsRecipe = "item_details_recipe_items"
|
||||
TableItems = "items"
|
||||
TableItemDetailsRecipe = "item_details_recipe_items"
|
||||
)
|
@ -9,13 +9,13 @@ import (
|
||||
|
||||
// RecipeManager provides high-level recipe system management with database integration
|
||||
type RecipeManager struct {
|
||||
db *database.DB
|
||||
masterRecipeList *MasterRecipeList
|
||||
masterRecipeBookList *MasterRecipeBookList
|
||||
loadedRecipes map[int32]*Recipe
|
||||
loadedRecipeBooks map[int32]*Recipe
|
||||
mu sync.RWMutex
|
||||
statisticsEnabled bool
|
||||
db *database.DB
|
||||
masterRecipeList *MasterRecipeList
|
||||
masterRecipeBookList *MasterRecipeBookList
|
||||
loadedRecipes map[int32]*Recipe
|
||||
loadedRecipeBooks map[int32]*Recipe
|
||||
mu sync.RWMutex
|
||||
statisticsEnabled bool
|
||||
|
||||
// Statistics
|
||||
stats RecipeManagerStats
|
||||
@ -34,12 +34,12 @@ type RecipeManagerStats struct {
|
||||
// NewRecipeManager creates a new recipe manager with database integration
|
||||
func NewRecipeManager(db *database.DB) *RecipeManager {
|
||||
return &RecipeManager{
|
||||
db: db,
|
||||
masterRecipeList: NewMasterRecipeList(),
|
||||
masterRecipeBookList: NewMasterRecipeBookList(),
|
||||
loadedRecipes: make(map[int32]*Recipe),
|
||||
loadedRecipeBooks: make(map[int32]*Recipe),
|
||||
statisticsEnabled: true,
|
||||
db: db,
|
||||
masterRecipeList: NewMasterRecipeList(),
|
||||
masterRecipeBookList: NewMasterRecipeBookList(),
|
||||
loadedRecipes: make(map[int32]*Recipe),
|
||||
loadedRecipeBooks: make(map[int32]*Recipe),
|
||||
statisticsEnabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,14 +8,14 @@ import (
|
||||
// MasterRecipeList manages all recipes in the system
|
||||
// Converted from C++ MasterRecipeList class
|
||||
type MasterRecipeList struct {
|
||||
recipes map[int32]*Recipe // Recipe ID -> Recipe
|
||||
recipesCRC map[int32]*Recipe // SOE CRC ID -> Recipe
|
||||
nameIndex map[string]*Recipe // Lowercase name -> Recipe
|
||||
bookIndex map[string][]*Recipe // Lowercase book name -> Recipes
|
||||
skillIndex map[int32][]*Recipe // Skill ID -> Recipes
|
||||
tierIndex map[int8][]*Recipe // Tier -> Recipes
|
||||
mutex sync.RWMutex
|
||||
stats *Statistics
|
||||
recipes map[int32]*Recipe // Recipe ID -> Recipe
|
||||
recipesCRC map[int32]*Recipe // SOE CRC ID -> Recipe
|
||||
nameIndex map[string]*Recipe // Lowercase name -> Recipe
|
||||
bookIndex map[string][]*Recipe // Lowercase book name -> Recipes
|
||||
skillIndex map[int32][]*Recipe // Skill ID -> Recipes
|
||||
tierIndex map[int8][]*Recipe // Tier -> Recipes
|
||||
mutex sync.RWMutex
|
||||
stats *Statistics
|
||||
}
|
||||
|
||||
// NewMasterRecipeList creates a new master recipe list
|
||||
|
@ -3,7 +3,6 @@ package recipes
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewRecipe creates a new recipe with default values
|
||||
|
@ -25,7 +25,7 @@ type RecipeProducts struct {
|
||||
type Recipe struct {
|
||||
// Core recipe data
|
||||
ID int32
|
||||
SoeID int32 // SOE recipe ID (CRC)
|
||||
SoeID int32 // SOE recipe ID (CRC)
|
||||
BookID int32
|
||||
Name string
|
||||
Description string
|
||||
@ -34,14 +34,14 @@ type Recipe struct {
|
||||
Device string
|
||||
|
||||
// Recipe requirements and properties
|
||||
Level int8
|
||||
Tier int8
|
||||
Icon int16
|
||||
Skill int32
|
||||
Technique int32
|
||||
Knowledge int32
|
||||
Classes int32 // Bitmask of tradeskill classes
|
||||
DeviceSubType int8
|
||||
Level int8
|
||||
Tier int8
|
||||
Icon int16
|
||||
Skill int32
|
||||
Technique int32
|
||||
Knowledge int32
|
||||
Classes int32 // Bitmask of tradeskill classes
|
||||
DeviceSubType int8
|
||||
|
||||
// Unknown fields from C++ (preserved for compatibility)
|
||||
Unknown1 int8
|
||||
@ -87,15 +87,15 @@ type Recipe struct {
|
||||
|
||||
// Statistics tracks recipe system usage patterns
|
||||
type Statistics struct {
|
||||
TotalRecipes int32
|
||||
TotalRecipeBooks int32
|
||||
RecipesByTier map[int8]int32
|
||||
RecipesBySkill map[int32]int32
|
||||
RecipeLookups int64
|
||||
RecipeBookLookups int64
|
||||
PlayerRecipeLoads int64
|
||||
ComponentQueries int64
|
||||
mutex sync.RWMutex
|
||||
TotalRecipes int32
|
||||
TotalRecipeBooks int32
|
||||
RecipesByTier map[int8]int32
|
||||
RecipesBySkill map[int32]int32
|
||||
RecipeLookups int64
|
||||
RecipeBookLookups int64
|
||||
PlayerRecipeLoads int64
|
||||
ComponentQueries int64
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewStatistics creates a new statistics tracker
|
||||
@ -140,14 +140,14 @@ func (s *Statistics) GetSnapshot() Statistics {
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
snapshot := Statistics{
|
||||
TotalRecipes: s.TotalRecipes,
|
||||
TotalRecipeBooks: s.TotalRecipeBooks,
|
||||
RecipesByTier: make(map[int8]int32),
|
||||
RecipesBySkill: make(map[int32]int32),
|
||||
RecipeLookups: s.RecipeLookups,
|
||||
RecipeBookLookups: s.RecipeBookLookups,
|
||||
PlayerRecipeLoads: s.PlayerRecipeLoads,
|
||||
ComponentQueries: s.ComponentQueries,
|
||||
TotalRecipes: s.TotalRecipes,
|
||||
TotalRecipeBooks: s.TotalRecipeBooks,
|
||||
RecipesByTier: make(map[int8]int32),
|
||||
RecipesBySkill: make(map[int32]int32),
|
||||
RecipeLookups: s.RecipeLookups,
|
||||
RecipeBookLookups: s.RecipeBookLookups,
|
||||
PlayerRecipeLoads: s.PlayerRecipeLoads,
|
||||
ComponentQueries: s.ComponentQueries,
|
||||
}
|
||||
|
||||
for tier, count := range s.RecipesByTier {
|
||||
|
@ -27,9 +27,9 @@ type RuleType int32
|
||||
|
||||
// CLIENT RULES
|
||||
const (
|
||||
ClientShowWelcomeScreen RuleType = 0 // Show welcome screen to new players
|
||||
ClientGroupSpellsTimer RuleType = 1 // Group spells update timer
|
||||
ClientQuestQueueTimer RuleType = 2 // Quest queue processing timer
|
||||
ClientShowWelcomeScreen RuleType = 0 // Show welcome screen to new players
|
||||
ClientGroupSpellsTimer RuleType = 1 // Group spells update timer
|
||||
ClientQuestQueueTimer RuleType = 2 // Quest queue processing timer
|
||||
)
|
||||
|
||||
// FACTION RULES
|
||||
@ -45,262 +45,262 @@ const (
|
||||
|
||||
// PLAYER RULES
|
||||
const (
|
||||
PlayerMaxLevel RuleType = 0 // Maximum player level
|
||||
PlayerMaxLevelOverrideStatus RuleType = 1 // Status required to override max level
|
||||
PlayerMaxPlayers RuleType = 2 // Maximum players on server
|
||||
PlayerMaxPlayersOverrideStatus RuleType = 3 // Status required to override max players
|
||||
PlayerVitalityAmount RuleType = 4 // Vitality bonus amount
|
||||
PlayerVitalityFrequency RuleType = 5 // Vitality bonus frequency
|
||||
PlayerMaxAA RuleType = 6 // Maximum total AA points
|
||||
PlayerMaxClassAA RuleType = 7 // Maximum class AA points
|
||||
PlayerMaxSubclassAA RuleType = 8 // Maximum subclass AA points
|
||||
PlayerMaxShadowsAA RuleType = 9 // Maximum shadows AA points
|
||||
PlayerMaxHeroicAA RuleType = 10 // Maximum heroic AA points
|
||||
PlayerMaxTradeskillAA RuleType = 11 // Maximum tradeskill AA points
|
||||
PlayerMaxPrestigeAA RuleType = 12 // Maximum prestige AA points
|
||||
PlayerMaxTradeskillPrestigeAA RuleType = 13 // Maximum tradeskill prestige AA points
|
||||
PlayerMaxDragonAA RuleType = 14 // Maximum dragon AA points
|
||||
PlayerMinLastNameLevel RuleType = 15 // Minimum level for last name
|
||||
PlayerMaxLastNameLength RuleType = 16 // Maximum last name length
|
||||
PlayerMinLastNameLength RuleType = 17 // Minimum last name length
|
||||
PlayerDisableHouseAlignmentRequirement RuleType = 18 // Disable house alignment requirement
|
||||
PlayerMentorItemDecayRate RuleType = 19 // Item decay rate when mentoring
|
||||
PlayerTemporaryItemLogoutTime RuleType = 20 // Time for temporary items to decay
|
||||
PlayerHeirloomItemShareExpiration RuleType = 21 // Heirloom item sharing expiration
|
||||
PlayerSwimmingSkillMinSpeed RuleType = 22 // Minimum swimming speed
|
||||
PlayerSwimmingSkillMaxSpeed RuleType = 23 // Maximum swimming speed
|
||||
PlayerSwimmingSkillMinBreathLength RuleType = 24 // Minimum breath length
|
||||
PlayerSwimmingSkillMaxBreathLength RuleType = 25 // Maximum breath length
|
||||
PlayerAutoSkillUpBaseSkills RuleType = 26 // Auto-skill base skills on level
|
||||
PlayerMaxWeightStrengthMultiplier RuleType = 27 // Strength multiplier for max weight
|
||||
PlayerBaseWeight RuleType = 28 // Base weight for all classes
|
||||
PlayerWeightPercentImpact RuleType = 29 // Speed impact per weight percent
|
||||
PlayerWeightPercentCap RuleType = 30 // Maximum weight impact cap
|
||||
PlayerCoinWeightPerStone RuleType = 31 // Coin weight per stone
|
||||
PlayerWeightInflictsSpeed RuleType = 32 // Whether weight affects speed
|
||||
PlayerLevelMasterySkillMultiplier RuleType = 33 // Level mastery skill multiplier
|
||||
PlayerTraitTieringSelection RuleType = 34 // Trait tiering selection rules
|
||||
PlayerClassicTraitLevelTable RuleType = 35 // Use classic trait level table
|
||||
PlayerTraitFocusSelectLevel RuleType = 36 // Trait focus selection level
|
||||
PlayerTraitTrainingSelectLevel RuleType = 37 // Trait training selection level
|
||||
PlayerTraitRaceSelectLevel RuleType = 38 // Trait race selection level
|
||||
PlayerTraitCharacterSelectLevel RuleType = 39 // Trait character selection level
|
||||
PlayerStartHPBase RuleType = 40 // Starting HP base
|
||||
PlayerStartPowerBase RuleType = 41 // Starting power base
|
||||
PlayerStartHPLevelMod RuleType = 42 // HP level modifier
|
||||
PlayerStartPowerLevelMod RuleType = 43 // Power level modifier
|
||||
PlayerAllowEquipCombat RuleType = 44 // Allow equipment changes in combat
|
||||
PlayerMaxTargetCommandDistance RuleType = 45 // Max distance for target command
|
||||
PlayerMinSkillMultiplierValue RuleType = 46 // Min skill multiplier value
|
||||
PlayerHarvestSkillUpMultiplier RuleType = 47 // Harvest skill up multiplier
|
||||
PlayerMiniDingPercentage RuleType = 48 // Mini ding percentage
|
||||
PlayerMaxLevel RuleType = 0 // Maximum player level
|
||||
PlayerMaxLevelOverrideStatus RuleType = 1 // Status required to override max level
|
||||
PlayerMaxPlayers RuleType = 2 // Maximum players on server
|
||||
PlayerMaxPlayersOverrideStatus RuleType = 3 // Status required to override max players
|
||||
PlayerVitalityAmount RuleType = 4 // Vitality bonus amount
|
||||
PlayerVitalityFrequency RuleType = 5 // Vitality bonus frequency
|
||||
PlayerMaxAA RuleType = 6 // Maximum total AA points
|
||||
PlayerMaxClassAA RuleType = 7 // Maximum class AA points
|
||||
PlayerMaxSubclassAA RuleType = 8 // Maximum subclass AA points
|
||||
PlayerMaxShadowsAA RuleType = 9 // Maximum shadows AA points
|
||||
PlayerMaxHeroicAA RuleType = 10 // Maximum heroic AA points
|
||||
PlayerMaxTradeskillAA RuleType = 11 // Maximum tradeskill AA points
|
||||
PlayerMaxPrestigeAA RuleType = 12 // Maximum prestige AA points
|
||||
PlayerMaxTradeskillPrestigeAA RuleType = 13 // Maximum tradeskill prestige AA points
|
||||
PlayerMaxDragonAA RuleType = 14 // Maximum dragon AA points
|
||||
PlayerMinLastNameLevel RuleType = 15 // Minimum level for last name
|
||||
PlayerMaxLastNameLength RuleType = 16 // Maximum last name length
|
||||
PlayerMinLastNameLength RuleType = 17 // Minimum last name length
|
||||
PlayerDisableHouseAlignmentRequirement RuleType = 18 // Disable house alignment requirement
|
||||
PlayerMentorItemDecayRate RuleType = 19 // Item decay rate when mentoring
|
||||
PlayerTemporaryItemLogoutTime RuleType = 20 // Time for temporary items to decay
|
||||
PlayerHeirloomItemShareExpiration RuleType = 21 // Heirloom item sharing expiration
|
||||
PlayerSwimmingSkillMinSpeed RuleType = 22 // Minimum swimming speed
|
||||
PlayerSwimmingSkillMaxSpeed RuleType = 23 // Maximum swimming speed
|
||||
PlayerSwimmingSkillMinBreathLength RuleType = 24 // Minimum breath length
|
||||
PlayerSwimmingSkillMaxBreathLength RuleType = 25 // Maximum breath length
|
||||
PlayerAutoSkillUpBaseSkills RuleType = 26 // Auto-skill base skills on level
|
||||
PlayerMaxWeightStrengthMultiplier RuleType = 27 // Strength multiplier for max weight
|
||||
PlayerBaseWeight RuleType = 28 // Base weight for all classes
|
||||
PlayerWeightPercentImpact RuleType = 29 // Speed impact per weight percent
|
||||
PlayerWeightPercentCap RuleType = 30 // Maximum weight impact cap
|
||||
PlayerCoinWeightPerStone RuleType = 31 // Coin weight per stone
|
||||
PlayerWeightInflictsSpeed RuleType = 32 // Whether weight affects speed
|
||||
PlayerLevelMasterySkillMultiplier RuleType = 33 // Level mastery skill multiplier
|
||||
PlayerTraitTieringSelection RuleType = 34 // Trait tiering selection rules
|
||||
PlayerClassicTraitLevelTable RuleType = 35 // Use classic trait level table
|
||||
PlayerTraitFocusSelectLevel RuleType = 36 // Trait focus selection level
|
||||
PlayerTraitTrainingSelectLevel RuleType = 37 // Trait training selection level
|
||||
PlayerTraitRaceSelectLevel RuleType = 38 // Trait race selection level
|
||||
PlayerTraitCharacterSelectLevel RuleType = 39 // Trait character selection level
|
||||
PlayerStartHPBase RuleType = 40 // Starting HP base
|
||||
PlayerStartPowerBase RuleType = 41 // Starting power base
|
||||
PlayerStartHPLevelMod RuleType = 42 // HP level modifier
|
||||
PlayerStartPowerLevelMod RuleType = 43 // Power level modifier
|
||||
PlayerAllowEquipCombat RuleType = 44 // Allow equipment changes in combat
|
||||
PlayerMaxTargetCommandDistance RuleType = 45 // Max distance for target command
|
||||
PlayerMinSkillMultiplierValue RuleType = 46 // Min skill multiplier value
|
||||
PlayerHarvestSkillUpMultiplier RuleType = 47 // Harvest skill up multiplier
|
||||
PlayerMiniDingPercentage RuleType = 48 // Mini ding percentage
|
||||
)
|
||||
|
||||
// PVP RULES
|
||||
const (
|
||||
PVPAllowPVP RuleType = 0 // Allow PVP combat
|
||||
PVPLevelRange RuleType = 1 // PVP level range
|
||||
PVPInvisPlayerDiscoveryRange RuleType = 2 // Invisible player discovery range
|
||||
PVPMitigationModByLevel RuleType = 3 // PVP mitigation modifier by level
|
||||
PVPType RuleType = 4 // PVP type (FFA, alignment, etc.)
|
||||
PVPAllowPVP RuleType = 0 // Allow PVP combat
|
||||
PVPLevelRange RuleType = 1 // PVP level range
|
||||
PVPInvisPlayerDiscoveryRange RuleType = 2 // Invisible player discovery range
|
||||
PVPMitigationModByLevel RuleType = 3 // PVP mitigation modifier by level
|
||||
PVPType RuleType = 4 // PVP type (FFA, alignment, etc.)
|
||||
)
|
||||
|
||||
// COMBAT RULES
|
||||
const (
|
||||
CombatMaxRange RuleType = 0 // Maximum combat range
|
||||
CombatDeathExperienceDebt RuleType = 1 // Experience debt on death
|
||||
CombatGroupExperienceDebt RuleType = 2 // Share debt with group
|
||||
CombatPVPDeathExperienceDebt RuleType = 3 // PVP death experience debt
|
||||
CombatExperienceToDebt RuleType = 4 // Percentage of experience to debt
|
||||
CombatExperienceDebtRecoveryPercent RuleType = 5 // Debt recovery percentage
|
||||
CombatExperienceDebtRecoveryPeriod RuleType = 6 // Debt recovery period
|
||||
CombatEnableSpiritShards RuleType = 7 // Enable spirit shards
|
||||
CombatSpiritShardSpawnScript RuleType = 8 // Spirit shard spawn script
|
||||
CombatShardDebtRecoveryPercent RuleType = 9 // Shard debt recovery percentage
|
||||
CombatShardRecoveryByRadius RuleType = 10 // Shard recovery by radius
|
||||
CombatShardLifetime RuleType = 11 // Shard lifetime
|
||||
CombatEffectiveMitigationCapLevel RuleType = 12 // Effective mitigation cap level
|
||||
CombatCalculatedMitigationCapLevel RuleType = 13 // Calculated mitigation cap level
|
||||
CombatMaxRange RuleType = 0 // Maximum combat range
|
||||
CombatDeathExperienceDebt RuleType = 1 // Experience debt on death
|
||||
CombatGroupExperienceDebt RuleType = 2 // Share debt with group
|
||||
CombatPVPDeathExperienceDebt RuleType = 3 // PVP death experience debt
|
||||
CombatExperienceToDebt RuleType = 4 // Percentage of experience to debt
|
||||
CombatExperienceDebtRecoveryPercent RuleType = 5 // Debt recovery percentage
|
||||
CombatExperienceDebtRecoveryPeriod RuleType = 6 // Debt recovery period
|
||||
CombatEnableSpiritShards RuleType = 7 // Enable spirit shards
|
||||
CombatSpiritShardSpawnScript RuleType = 8 // Spirit shard spawn script
|
||||
CombatShardDebtRecoveryPercent RuleType = 9 // Shard debt recovery percentage
|
||||
CombatShardRecoveryByRadius RuleType = 10 // Shard recovery by radius
|
||||
CombatShardLifetime RuleType = 11 // Shard lifetime
|
||||
CombatEffectiveMitigationCapLevel RuleType = 12 // Effective mitigation cap level
|
||||
CombatCalculatedMitigationCapLevel RuleType = 13 // Calculated mitigation cap level
|
||||
CombatMitigationLevelEffectivenessMax RuleType = 14 // Max mitigation effectiveness
|
||||
CombatMitigationLevelEffectivenessMin RuleType = 15 // Min mitigation effectiveness
|
||||
CombatMaxMitigationAllowed RuleType = 16 // Max mitigation allowed PVE
|
||||
CombatMaxMitigationAllowedPVP RuleType = 17 // Max mitigation allowed PVP
|
||||
CombatStrengthNPC RuleType = 18 // NPC strength multiplier
|
||||
CombatStrengthOther RuleType = 19 // Other strength multiplier
|
||||
CombatMaxSkillBonusByLevel RuleType = 20 // Max skill bonus by level
|
||||
CombatLockedEncounterNoAttack RuleType = 21 // Locked encounter no attack
|
||||
CombatMaxChaseDistance RuleType = 22 // Maximum chase distance
|
||||
CombatMaxMitigationAllowed RuleType = 16 // Max mitigation allowed PVE
|
||||
CombatMaxMitigationAllowedPVP RuleType = 17 // Max mitigation allowed PVP
|
||||
CombatStrengthNPC RuleType = 18 // NPC strength multiplier
|
||||
CombatStrengthOther RuleType = 19 // Other strength multiplier
|
||||
CombatMaxSkillBonusByLevel RuleType = 20 // Max skill bonus by level
|
||||
CombatLockedEncounterNoAttack RuleType = 21 // Locked encounter no attack
|
||||
CombatMaxChaseDistance RuleType = 22 // Maximum chase distance
|
||||
)
|
||||
|
||||
// SPAWN RULES
|
||||
const (
|
||||
SpawnSpeedMultiplier RuleType = 0 // Speed multiplier
|
||||
SpawnClassicRegen RuleType = 1 // Use classic regeneration
|
||||
SpawnHailMovementPause RuleType = 2 // Hail movement pause time
|
||||
SpawnHailDistance RuleType = 3 // Hail distance
|
||||
SpawnUseHardCodeWaterModelType RuleType = 4 // Use hardcoded water model type
|
||||
SpawnSpeedMultiplier RuleType = 0 // Speed multiplier
|
||||
SpawnClassicRegen RuleType = 1 // Use classic regeneration
|
||||
SpawnHailMovementPause RuleType = 2 // Hail movement pause time
|
||||
SpawnHailDistance RuleType = 3 // Hail distance
|
||||
SpawnUseHardCodeWaterModelType RuleType = 4 // Use hardcoded water model type
|
||||
SpawnUseHardCodeFlyingModelType RuleType = 5 // Use hardcoded flying model type
|
||||
)
|
||||
|
||||
// UI RULES
|
||||
const (
|
||||
UIMaxWhoResults RuleType = 0 // Maximum /who results
|
||||
UIMaxWhoOverrideStatus RuleType = 1 // Status to override max /who results
|
||||
UIMaxWhoResults RuleType = 0 // Maximum /who results
|
||||
UIMaxWhoOverrideStatus RuleType = 1 // Status to override max /who results
|
||||
)
|
||||
|
||||
// WORLD RULES
|
||||
const (
|
||||
WorldDefaultStartingZoneID RuleType = 0 // Default starting zone ID
|
||||
WorldEnablePOIDiscovery RuleType = 1 // Enable POI discovery
|
||||
WorldGamblingTokenItemID RuleType = 2 // Gambling token item ID
|
||||
WorldGuildAutoJoin RuleType = 3 // Auto join guild
|
||||
WorldGuildAutoJoinID RuleType = 4 // Auto join guild ID
|
||||
WorldGuildAutoJoinDefaultRankID RuleType = 5 // Auto join default rank ID
|
||||
WorldServerLocked RuleType = 6 // Server locked
|
||||
WorldServerLockedOverrideStatus RuleType = 7 // Server locked override status
|
||||
WorldSyncZonesWithLogin RuleType = 8 // Sync zones with login
|
||||
WorldSyncEquipWithLogin RuleType = 9 // Sync equipment with login
|
||||
WorldUseBannedIPsTable RuleType = 10 // Use banned IPs table
|
||||
WorldLinkDeadTimer RuleType = 11 // Link dead timer
|
||||
WorldDefaultStartingZoneID RuleType = 0 // Default starting zone ID
|
||||
WorldEnablePOIDiscovery RuleType = 1 // Enable POI discovery
|
||||
WorldGamblingTokenItemID RuleType = 2 // Gambling token item ID
|
||||
WorldGuildAutoJoin RuleType = 3 // Auto join guild
|
||||
WorldGuildAutoJoinID RuleType = 4 // Auto join guild ID
|
||||
WorldGuildAutoJoinDefaultRankID RuleType = 5 // Auto join default rank ID
|
||||
WorldServerLocked RuleType = 6 // Server locked
|
||||
WorldServerLockedOverrideStatus RuleType = 7 // Server locked override status
|
||||
WorldSyncZonesWithLogin RuleType = 8 // Sync zones with login
|
||||
WorldSyncEquipWithLogin RuleType = 9 // Sync equipment with login
|
||||
WorldUseBannedIPsTable RuleType = 10 // Use banned IPs table
|
||||
WorldLinkDeadTimer RuleType = 11 // Link dead timer
|
||||
WorldRemoveDisconnectedClientsTimer RuleType = 12 // Remove disconnected clients timer
|
||||
WorldPlayerCampTimer RuleType = 13 // Player camp timer
|
||||
WorldGMCampTimer RuleType = 14 // GM camp timer
|
||||
WorldAutoAdminPlayers RuleType = 15 // Auto admin players
|
||||
WorldAutoAdminGMs RuleType = 16 // Auto admin GMs
|
||||
WorldAutoAdminStatusValue RuleType = 17 // Auto admin status value
|
||||
WorldDuskTime RuleType = 18 // Dusk time
|
||||
WorldDawnTime RuleType = 19 // Dawn time
|
||||
WorldThreadedLoad RuleType = 20 // Threaded loading
|
||||
WorldTradeskillSuccessChance RuleType = 21 // Tradeskill success chance
|
||||
WorldTradeskillCritSuccessChance RuleType = 22 // Tradeskill critical success chance
|
||||
WorldTradeskillFailChance RuleType = 23 // Tradeskill fail chance
|
||||
WorldTradeskillCritFailChance RuleType = 24 // Tradeskill critical fail chance
|
||||
WorldTradeskillEventChance RuleType = 25 // Tradeskill event chance
|
||||
WorldEditorURL RuleType = 26 // Editor URL
|
||||
WorldEditorIncludeID RuleType = 27 // Editor include ID
|
||||
WorldEditorOfficialServer RuleType = 28 // Editor official server
|
||||
WorldSavePaperdollImage RuleType = 29 // Save paperdoll image
|
||||
WorldSaveHeadshotImage RuleType = 30 // Save headshot image
|
||||
WorldSendPaperdollImagesToLogin RuleType = 31 // Send paperdoll images to login
|
||||
WorldTreasureChestDisabled RuleType = 32 // Treasure chest disabled
|
||||
WorldStartingZoneLanguages RuleType = 33 // Starting zone languages
|
||||
WorldStartingZoneRuleFlag RuleType = 34 // Starting zone rule flag
|
||||
WorldEnforceRacialAlignment RuleType = 35 // Enforce racial alignment
|
||||
WorldMemoryCacheZoneMaps RuleType = 36 // Memory cache zone maps
|
||||
WorldAutoLockEncounter RuleType = 37 // Auto lock encounter
|
||||
WorldDisplayItemTiers RuleType = 38 // Display item tiers
|
||||
WorldLoreAndLegendAccept RuleType = 39 // Lore and legend accept
|
||||
WorldPlayerCampTimer RuleType = 13 // Player camp timer
|
||||
WorldGMCampTimer RuleType = 14 // GM camp timer
|
||||
WorldAutoAdminPlayers RuleType = 15 // Auto admin players
|
||||
WorldAutoAdminGMs RuleType = 16 // Auto admin GMs
|
||||
WorldAutoAdminStatusValue RuleType = 17 // Auto admin status value
|
||||
WorldDuskTime RuleType = 18 // Dusk time
|
||||
WorldDawnTime RuleType = 19 // Dawn time
|
||||
WorldThreadedLoad RuleType = 20 // Threaded loading
|
||||
WorldTradeskillSuccessChance RuleType = 21 // Tradeskill success chance
|
||||
WorldTradeskillCritSuccessChance RuleType = 22 // Tradeskill critical success chance
|
||||
WorldTradeskillFailChance RuleType = 23 // Tradeskill fail chance
|
||||
WorldTradeskillCritFailChance RuleType = 24 // Tradeskill critical fail chance
|
||||
WorldTradeskillEventChance RuleType = 25 // Tradeskill event chance
|
||||
WorldEditorURL RuleType = 26 // Editor URL
|
||||
WorldEditorIncludeID RuleType = 27 // Editor include ID
|
||||
WorldEditorOfficialServer RuleType = 28 // Editor official server
|
||||
WorldSavePaperdollImage RuleType = 29 // Save paperdoll image
|
||||
WorldSaveHeadshotImage RuleType = 30 // Save headshot image
|
||||
WorldSendPaperdollImagesToLogin RuleType = 31 // Send paperdoll images to login
|
||||
WorldTreasureChestDisabled RuleType = 32 // Treasure chest disabled
|
||||
WorldStartingZoneLanguages RuleType = 33 // Starting zone languages
|
||||
WorldStartingZoneRuleFlag RuleType = 34 // Starting zone rule flag
|
||||
WorldEnforceRacialAlignment RuleType = 35 // Enforce racial alignment
|
||||
WorldMemoryCacheZoneMaps RuleType = 36 // Memory cache zone maps
|
||||
WorldAutoLockEncounter RuleType = 37 // Auto lock encounter
|
||||
WorldDisplayItemTiers RuleType = 38 // Display item tiers
|
||||
WorldLoreAndLegendAccept RuleType = 39 // Lore and legend accept
|
||||
)
|
||||
|
||||
// ZONE RULES
|
||||
const (
|
||||
ZoneMinLevelOverrideStatus RuleType = 0 // Min level override status
|
||||
ZoneMinAccessOverrideStatus RuleType = 1 // Min access override status
|
||||
ZoneXPMultiplier RuleType = 2 // Experience multiplier
|
||||
ZoneTSXPMultiplier RuleType = 3 // Tradeskill experience multiplier
|
||||
ZoneWeatherEnabled RuleType = 4 // Weather enabled
|
||||
ZoneWeatherType RuleType = 5 // Weather type
|
||||
ZoneMinWeatherSeverity RuleType = 6 // Min weather severity
|
||||
ZoneMaxWeatherSeverity RuleType = 7 // Max weather severity
|
||||
ZoneWeatherChangeFrequency RuleType = 8 // Weather change frequency
|
||||
ZoneWeatherChangePerInterval RuleType = 9 // Weather change per interval
|
||||
ZoneWeatherDynamicMaxOffset RuleType = 10 // Weather dynamic max offset
|
||||
ZoneWeatherChangeChance RuleType = 11 // Weather change chance
|
||||
ZoneSpawnUpdateTimer RuleType = 12 // Spawn update timer
|
||||
ZoneCheckAttackPlayer RuleType = 13 // Check attack player
|
||||
ZoneCheckAttackNPC RuleType = 14 // Check attack NPC
|
||||
ZoneHOTime RuleType = 15 // Heroic opportunity time
|
||||
ZoneUseMapUnderworldCoords RuleType = 16 // Use map underworld coords
|
||||
ZoneMapUnderworldCoordOffset RuleType = 17 // Map underworld coord offset
|
||||
ZoneSharedMaxPlayers RuleType = 18 // Shared zone max players
|
||||
ZoneRegenTimer RuleType = 19 // Regeneration timer
|
||||
ZoneClientSaveTimer RuleType = 20 // Client save timer
|
||||
ZoneShutdownDelayTimer RuleType = 21 // Shutdown delay timer
|
||||
ZoneWeatherTimer RuleType = 22 // Weather timer
|
||||
ZoneSpawnDeleteTimer RuleType = 23 // Spawn delete timer
|
||||
ZoneMinLevelOverrideStatus RuleType = 0 // Min level override status
|
||||
ZoneMinAccessOverrideStatus RuleType = 1 // Min access override status
|
||||
ZoneXPMultiplier RuleType = 2 // Experience multiplier
|
||||
ZoneTSXPMultiplier RuleType = 3 // Tradeskill experience multiplier
|
||||
ZoneWeatherEnabled RuleType = 4 // Weather enabled
|
||||
ZoneWeatherType RuleType = 5 // Weather type
|
||||
ZoneMinWeatherSeverity RuleType = 6 // Min weather severity
|
||||
ZoneMaxWeatherSeverity RuleType = 7 // Max weather severity
|
||||
ZoneWeatherChangeFrequency RuleType = 8 // Weather change frequency
|
||||
ZoneWeatherChangePerInterval RuleType = 9 // Weather change per interval
|
||||
ZoneWeatherDynamicMaxOffset RuleType = 10 // Weather dynamic max offset
|
||||
ZoneWeatherChangeChance RuleType = 11 // Weather change chance
|
||||
ZoneSpawnUpdateTimer RuleType = 12 // Spawn update timer
|
||||
ZoneCheckAttackPlayer RuleType = 13 // Check attack player
|
||||
ZoneCheckAttackNPC RuleType = 14 // Check attack NPC
|
||||
ZoneHOTime RuleType = 15 // Heroic opportunity time
|
||||
ZoneUseMapUnderworldCoords RuleType = 16 // Use map underworld coords
|
||||
ZoneMapUnderworldCoordOffset RuleType = 17 // Map underworld coord offset
|
||||
ZoneSharedMaxPlayers RuleType = 18 // Shared zone max players
|
||||
ZoneRegenTimer RuleType = 19 // Regeneration timer
|
||||
ZoneClientSaveTimer RuleType = 20 // Client save timer
|
||||
ZoneShutdownDelayTimer RuleType = 21 // Shutdown delay timer
|
||||
ZoneWeatherTimer RuleType = 22 // Weather timer
|
||||
ZoneSpawnDeleteTimer RuleType = 23 // Spawn delete timer
|
||||
)
|
||||
|
||||
// LOOT RULES
|
||||
const (
|
||||
LootRadius RuleType = 0 // Loot pickup radius
|
||||
LootAutoDisarmChest RuleType = 1 // Auto disarm chest
|
||||
LootChestTriggerRadiusGroup RuleType = 2 // Chest trigger radius group
|
||||
LootChestUnlockedTimeDrop RuleType = 3 // Chest unlocked time drop
|
||||
LootRadius RuleType = 0 // Loot pickup radius
|
||||
LootAutoDisarmChest RuleType = 1 // Auto disarm chest
|
||||
LootChestTriggerRadiusGroup RuleType = 2 // Chest trigger radius group
|
||||
LootChestUnlockedTimeDrop RuleType = 3 // Chest unlocked time drop
|
||||
LootAllowChestUnlockByDropTime RuleType = 4 // Allow chest unlock by drop time
|
||||
LootChestUnlockedTimeTrap RuleType = 5 // Chest unlocked time trap
|
||||
LootChestUnlockedTimeTrap RuleType = 5 // Chest unlocked time trap
|
||||
LootAllowChestUnlockByTrapTime RuleType = 6 // Allow chest unlock by trap time
|
||||
LootSkipGrayMob RuleType = 7 // Skip loot from gray mobs
|
||||
LootDistributionTime RuleType = 8 // Loot distribution time
|
||||
LootSkipGrayMob RuleType = 7 // Skip loot from gray mobs
|
||||
LootDistributionTime RuleType = 8 // Loot distribution time
|
||||
)
|
||||
|
||||
// SPELLS RULES
|
||||
const (
|
||||
SpellsNoInterruptBaseChance RuleType = 0 // No interrupt base chance
|
||||
SpellsEnableFizzleSpells RuleType = 1 // Enable fizzle spells
|
||||
SpellsDefaultFizzleChance RuleType = 2 // Default fizzle chance
|
||||
SpellsFizzleMaxSkill RuleType = 3 // Fizzle max skill
|
||||
SpellsFizzleDefaultSkill RuleType = 4 // Fizzle default skill
|
||||
SpellsEnableCrossZoneGroupBuffs RuleType = 5 // Enable cross zone group buffs
|
||||
SpellsEnableCrossZoneTargetBuffs RuleType = 6 // Enable cross zone target buffs
|
||||
SpellsNoInterruptBaseChance RuleType = 0 // No interrupt base chance
|
||||
SpellsEnableFizzleSpells RuleType = 1 // Enable fizzle spells
|
||||
SpellsDefaultFizzleChance RuleType = 2 // Default fizzle chance
|
||||
SpellsFizzleMaxSkill RuleType = 3 // Fizzle max skill
|
||||
SpellsFizzleDefaultSkill RuleType = 4 // Fizzle default skill
|
||||
SpellsEnableCrossZoneGroupBuffs RuleType = 5 // Enable cross zone group buffs
|
||||
SpellsEnableCrossZoneTargetBuffs RuleType = 6 // Enable cross zone target buffs
|
||||
SpellsPlayerSpellSaveStateWaitInterval RuleType = 7 // Player spell save state wait interval
|
||||
SpellsPlayerSpellSaveStateCap RuleType = 8 // Player spell save state cap
|
||||
SpellsRequirePreviousTierScribe RuleType = 9 // Require previous tier scribe
|
||||
SpellsCureSpellID RuleType = 10 // Cure spell ID
|
||||
SpellsCureCurseSpellID RuleType = 11 // Cure curse spell ID
|
||||
SpellsCureNoxiousSpellID RuleType = 12 // Cure noxious spell ID
|
||||
SpellsCureMagicSpellID RuleType = 13 // Cure magic spell ID
|
||||
SpellsCureTraumaSpellID RuleType = 14 // Cure trauma spell ID
|
||||
SpellsCureArcaneSpellID RuleType = 15 // Cure arcane spell ID
|
||||
SpellsMinistrationSkillID RuleType = 16 // Ministration skill ID
|
||||
SpellsMinistrationPowerReductionMax RuleType = 17 // Ministration power reduction max
|
||||
SpellsMinistrationPowerReductionSkill RuleType = 18 // Ministration power reduction skill
|
||||
SpellsMasterSkillReduceSpellResist RuleType = 19 // Master skill reduce spell resist
|
||||
SpellsUseClassicSpellLevel RuleType = 20 // Use classic spell level
|
||||
SpellsPlayerSpellSaveStateCap RuleType = 8 // Player spell save state cap
|
||||
SpellsRequirePreviousTierScribe RuleType = 9 // Require previous tier scribe
|
||||
SpellsCureSpellID RuleType = 10 // Cure spell ID
|
||||
SpellsCureCurseSpellID RuleType = 11 // Cure curse spell ID
|
||||
SpellsCureNoxiousSpellID RuleType = 12 // Cure noxious spell ID
|
||||
SpellsCureMagicSpellID RuleType = 13 // Cure magic spell ID
|
||||
SpellsCureTraumaSpellID RuleType = 14 // Cure trauma spell ID
|
||||
SpellsCureArcaneSpellID RuleType = 15 // Cure arcane spell ID
|
||||
SpellsMinistrationSkillID RuleType = 16 // Ministration skill ID
|
||||
SpellsMinistrationPowerReductionMax RuleType = 17 // Ministration power reduction max
|
||||
SpellsMinistrationPowerReductionSkill RuleType = 18 // Ministration power reduction skill
|
||||
SpellsMasterSkillReduceSpellResist RuleType = 19 // Master skill reduce spell resist
|
||||
SpellsUseClassicSpellLevel RuleType = 20 // Use classic spell level
|
||||
)
|
||||
|
||||
// EXPANSION RULES
|
||||
const (
|
||||
ExpansionGlobalFlag RuleType = 0 // Global expansion flag
|
||||
ExpansionGlobalFlag RuleType = 0 // Global expansion flag
|
||||
ExpansionHolidayFlag RuleType = 1 // Global holiday flag
|
||||
)
|
||||
|
||||
// DISCORD RULES
|
||||
const (
|
||||
DiscordEnabled RuleType = 0 // Discord enabled
|
||||
DiscordWebhookURL RuleType = 1 // Discord webhook URL
|
||||
DiscordBotToken RuleType = 2 // Discord bot token
|
||||
DiscordChannel RuleType = 3 // Discord channel
|
||||
DiscordListenChan RuleType = 4 // Discord listen channel
|
||||
DiscordEnabled RuleType = 0 // Discord enabled
|
||||
DiscordWebhookURL RuleType = 1 // Discord webhook URL
|
||||
DiscordBotToken RuleType = 2 // Discord bot token
|
||||
DiscordChannel RuleType = 3 // Discord channel
|
||||
DiscordListenChan RuleType = 4 // Discord listen channel
|
||||
)
|
||||
|
||||
// Rule validation constants
|
||||
const (
|
||||
MaxRuleValueLength = 1024 // Maximum rule value length
|
||||
MaxRuleValueLength = 1024 // Maximum rule value length
|
||||
MaxRuleCombinedLength = 2048 // Maximum combined rule string length
|
||||
MaxRuleNameLength = 64 // Maximum rule name length
|
||||
MaxRuleNameLength = 64 // Maximum rule name length
|
||||
)
|
||||
|
||||
// Database constants
|
||||
const (
|
||||
TableRuleSets = "rulesets"
|
||||
TableRuleSetDetails = "ruleset_details"
|
||||
TableVariables = "variables"
|
||||
DefaultRuleSetIDVar = "default_ruleset_id"
|
||||
TableRuleSets = "rulesets"
|
||||
TableRuleSetDetails = "ruleset_details"
|
||||
TableVariables = "variables"
|
||||
DefaultRuleSetIDVar = "default_ruleset_id"
|
||||
)
|
||||
|
||||
// Error variables
|
||||
var (
|
||||
ErrRuleNotFound = errors.New("rule not found")
|
||||
ErrRuleSetNotFound = errors.New("rule set not found")
|
||||
ErrInvalidRuleCategory = errors.New("invalid rule category")
|
||||
ErrInvalidRuleType = errors.New("invalid rule type")
|
||||
ErrInvalidRuleValue = errors.New("invalid rule value")
|
||||
ErrDuplicateRuleSet = errors.New("duplicate rule set")
|
||||
ErrRuleSetNotActive = errors.New("rule set not active")
|
||||
ErrGlobalRuleSetNotSet = errors.New("global rule set not set")
|
||||
ErrZoneRuleSetNotFound = errors.New("zone rule set not found")
|
||||
ErrRuleValueTooLong = errors.New("rule value too long")
|
||||
ErrRuleNameTooLong = errors.New("rule name too long")
|
||||
ErrRuleNotFound = errors.New("rule not found")
|
||||
ErrRuleSetNotFound = errors.New("rule set not found")
|
||||
ErrInvalidRuleCategory = errors.New("invalid rule category")
|
||||
ErrInvalidRuleType = errors.New("invalid rule type")
|
||||
ErrInvalidRuleValue = errors.New("invalid rule value")
|
||||
ErrDuplicateRuleSet = errors.New("duplicate rule set")
|
||||
ErrRuleSetNotActive = errors.New("rule set not active")
|
||||
ErrGlobalRuleSetNotSet = errors.New("global rule set not set")
|
||||
ErrZoneRuleSetNotFound = errors.New("zone rule set not found")
|
||||
ErrRuleValueTooLong = errors.New("rule value too long")
|
||||
ErrRuleNameTooLong = errors.New("rule name too long")
|
||||
)
|
||||
|
||||
// Rule category names for string conversion
|
||||
|
@ -126,12 +126,12 @@ type RuleCache interface {
|
||||
|
||||
// CacheStats contains cache performance statistics
|
||||
type CacheStats struct {
|
||||
Hits int64 // Number of cache hits
|
||||
Misses int64 // Number of cache misses
|
||||
Entries int64 // Number of cached entries
|
||||
Memory int64 // Memory usage in bytes
|
||||
Hits int64 // Number of cache hits
|
||||
Misses int64 // Number of cache misses
|
||||
Entries int64 // Number of cached entries
|
||||
Memory int64 // Memory usage in bytes
|
||||
HitRatio float64 // Cache hit ratio
|
||||
LastCleared int64 // Timestamp of last cache clear
|
||||
LastCleared int64 // Timestamp of last cache clear
|
||||
}
|
||||
|
||||
// RuleManagerAdapter provides a simplified interface to the rule manager
|
||||
@ -219,10 +219,10 @@ func (rma *RuleManagerAdapter) GetZoneID() int32 {
|
||||
|
||||
// RuleServiceConfig contains configuration for rule services
|
||||
type RuleServiceConfig struct {
|
||||
DatabaseEnabled bool // Whether to use database storage
|
||||
CacheEnabled bool // Whether to enable rule caching
|
||||
CacheTTL int64 // Cache TTL in seconds
|
||||
MaxCacheSize int64 // Maximum cache size in bytes
|
||||
DatabaseEnabled bool // Whether to use database storage
|
||||
CacheEnabled bool // Whether to enable rule caching
|
||||
CacheTTL int64 // Cache TTL in seconds
|
||||
MaxCacheSize int64 // Maximum cache size in bytes
|
||||
EventHandlers []RuleEventHandler // Event handlers to register
|
||||
Validators []RuleValidator // Validators to register
|
||||
}
|
||||
|
@ -10,11 +10,11 @@ import (
|
||||
// Converted from C++ RuleManager class
|
||||
type RuleManager struct {
|
||||
// Core rule storage
|
||||
rules map[RuleCategory]map[RuleType]*Rule // Default rules from code
|
||||
ruleSets map[int32]*RuleSet // Rule sets loaded from database
|
||||
globalRuleSet *RuleSet // Active global rule set
|
||||
zoneRuleSets map[int32]*RuleSet // Zone-specific rule sets
|
||||
blankRule *Rule // Default blank rule
|
||||
rules map[RuleCategory]map[RuleType]*Rule // Default rules from code
|
||||
ruleSets map[int32]*RuleSet // Rule sets loaded from database
|
||||
globalRuleSet *RuleSet // Active global rule set
|
||||
zoneRuleSets map[int32]*RuleSet // Zone-specific rule sets
|
||||
blankRule *Rule // Default blank rule
|
||||
|
||||
// Thread safety
|
||||
rulesMutex sync.RWMutex
|
||||
|
@ -8,10 +8,10 @@ const (
|
||||
|
||||
// Default spawn settings for signs
|
||||
const (
|
||||
DefaultSpawnType = 2 // Signs are spawn type 2
|
||||
DefaultActivityStatus = 64 // Activity status for signs
|
||||
DefaultPosState = 1 // Position state
|
||||
DefaultDifficulty = 0 // No difficulty for signs
|
||||
DefaultSpawnType = 2 // Signs are spawn type 2
|
||||
DefaultActivityStatus = 64 // Activity status for signs
|
||||
DefaultPosState = 1 // Position state
|
||||
DefaultDifficulty = 0 // No difficulty for signs
|
||||
)
|
||||
|
||||
// Channel colors for messages (these would be defined elsewhere in a real implementation)
|
||||
|
@ -7,30 +7,30 @@ import (
|
||||
|
||||
// Manager provides high-level management of the sign system
|
||||
type Manager struct {
|
||||
signs map[int32]*Sign // Signs by ID
|
||||
signsByZone map[int32][]*Sign // Signs by zone ID
|
||||
signsByWidget map[int32]*Sign // Signs by widget ID
|
||||
database Database
|
||||
logger Logger
|
||||
mutex sync.RWMutex
|
||||
signs map[int32]*Sign // Signs by ID
|
||||
signsByZone map[int32][]*Sign // Signs by zone ID
|
||||
signsByWidget map[int32]*Sign // Signs by widget ID
|
||||
database Database
|
||||
logger Logger
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Statistics
|
||||
totalSigns int64
|
||||
signsByType map[int8]int64 // Sign type -> count
|
||||
signInteractions int64
|
||||
zoneTransports int64
|
||||
transporterUses int64
|
||||
totalSigns int64
|
||||
signsByType map[int8]int64 // Sign type -> count
|
||||
signInteractions int64
|
||||
zoneTransports int64
|
||||
transporterUses int64
|
||||
}
|
||||
|
||||
// NewManager creates a new sign manager
|
||||
func NewManager(database Database, logger Logger) *Manager {
|
||||
return &Manager{
|
||||
signs: make(map[int32]*Sign),
|
||||
signsByZone: make(map[int32][]*Sign),
|
||||
signsByWidget: make(map[int32]*Sign),
|
||||
database: database,
|
||||
logger: logger,
|
||||
signsByType: make(map[int8]int64),
|
||||
signs: make(map[int32]*Sign),
|
||||
signsByZone: make(map[int32][]*Sign),
|
||||
signsByWidget: make(map[int32]*Sign),
|
||||
database: database,
|
||||
logger: logger,
|
||||
signsByType: make(map[int8]int64),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"eq2emu/internal/spawn"
|
||||
)
|
||||
|
||||
// Copy creates a deep copy of the sign with size randomization
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user