interface to any
This commit is contained in:
parent
41f80008c9
commit
5cb4b5b56c
@ -4,7 +4,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
"zombiezen.com/go/sqlite/sqlitex"
|
"zombiezen.com/go/sqlite/sqlitex"
|
||||||
@ -27,22 +27,22 @@ type Config struct {
|
|||||||
|
|
||||||
// Database wraps the SQL database connection
|
// Database wraps the SQL database connection
|
||||||
type Database struct {
|
type Database struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
pool *sqlitex.Pool // For achievements system compatibility (SQLite only)
|
pool *sqlitex.Pool // For achievements system compatibility (SQLite only)
|
||||||
config Config
|
config Config
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new database connection with the provided configuration
|
// New creates a new database connection with the provided configuration
|
||||||
func New(config Config) (*Database, error) {
|
func New(config Config) (*Database, error) {
|
||||||
var driverName string
|
var driverName string
|
||||||
var pool *sqlitex.Pool
|
var pool *sqlitex.Pool
|
||||||
|
|
||||||
// Set default pool size
|
// Set default pool size
|
||||||
if config.PoolSize == 0 {
|
if config.PoolSize == 0 {
|
||||||
config.PoolSize = 25
|
config.PoolSize = 25
|
||||||
}
|
}
|
||||||
|
|
||||||
switch config.Type {
|
switch config.Type {
|
||||||
case SQLite:
|
case SQLite:
|
||||||
driverName = "sqlite"
|
driverName = "sqlite"
|
||||||
@ -59,27 +59,27 @@ func New(config Config) (*Database, error) {
|
|||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported database type: %d", config.Type)
|
return nil, fmt.Errorf("unsupported database type: %d", config.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := sql.Open(driverName, config.DSN)
|
db, err := sql.Open(driverName, config.DSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test connection
|
// Test connection
|
||||||
if err := db.Ping(); err != nil {
|
if err := db.Ping(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set connection pool settings
|
// Set connection pool settings
|
||||||
db.SetMaxOpenConns(config.PoolSize)
|
db.SetMaxOpenConns(config.PoolSize)
|
||||||
db.SetMaxIdleConns(config.PoolSize / 5)
|
db.SetMaxIdleConns(config.PoolSize / 5)
|
||||||
|
|
||||||
d := &Database{
|
d := &Database{
|
||||||
db: db,
|
db: db,
|
||||||
pool: pool,
|
pool: pool,
|
||||||
config: config,
|
config: config,
|
||||||
}
|
}
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,19 +101,18 @@ func (d *Database) GetPool() *sqlitex.Pool {
|
|||||||
return d.pool
|
return d.pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Query executes a query that returns rows
|
// Query executes a query that returns rows
|
||||||
func (d *Database) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
func (d *Database) Query(query string, args ...any) (*sql.Rows, error) {
|
||||||
return d.db.Query(query, args...)
|
return d.db.Query(query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryRow executes a query that returns a single row
|
// QueryRow executes a query that returns a single row
|
||||||
func (d *Database) QueryRow(query string, args ...interface{}) *sql.Row {
|
func (d *Database) QueryRow(query string, args ...any) *sql.Row {
|
||||||
return d.db.QueryRow(query, args...)
|
return d.db.QueryRow(query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes a query that doesn't return rows
|
// Exec executes a query that doesn't return rows
|
||||||
func (d *Database) Exec(query string, args ...interface{}) (sql.Result, error) {
|
func (d *Database) Exec(query string, args ...any) (sql.Result, error) {
|
||||||
return d.db.Exec(query, args...)
|
return d.db.Exec(query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,21 +128,21 @@ func (d *Database) LoadRules() (map[string]map[string]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
rules := make(map[string]map[string]string)
|
rules := make(map[string]map[string]string)
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var category, name, value string
|
var category, name, value string
|
||||||
if err := rows.Scan(&category, &name, &value); err != nil {
|
if err := rows.Scan(&category, &name, &value); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if rules[category] == nil {
|
if rules[category] == nil {
|
||||||
rules[category] = make(map[string]string)
|
rules[category] = make(map[string]string)
|
||||||
}
|
}
|
||||||
rules[category][name] = value
|
rules[category][name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
return rules, rows.Err()
|
return rules, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +173,7 @@ func NewMySQL(dsn string) (*Database, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetZones retrieves all zones from the database
|
// GetZones retrieves all zones from the database
|
||||||
func (d *Database) GetZones() ([]map[string]interface{}, error) {
|
func (d *Database) GetZones() ([]map[string]any, error) {
|
||||||
rows, err := d.Query(`
|
rows, err := d.Query(`
|
||||||
SELECT id, name, file, description, motd, min_level, max_level,
|
SELECT id, name, file, description, motd, min_level, max_level,
|
||||||
min_version, xp_modifier, city_zone, weather_allowed,
|
min_version, xp_modifier, city_zone, weather_allowed,
|
||||||
@ -186,16 +185,16 @@ func (d *Database) GetZones() ([]map[string]interface{}, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var zones []map[string]interface{}
|
var zones []map[string]any
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
zone := make(map[string]interface{})
|
zone := make(map[string]any)
|
||||||
var id, minLevel, maxLevel, minVersion int
|
var id, minLevel, maxLevel, minVersion int
|
||||||
var name, file, description, motd string
|
var name, file, description, motd string
|
||||||
var xpModifier, safeX, safeY, safeZ, safeHeading float64
|
var xpModifier, safeX, safeY, safeZ, safeHeading float64
|
||||||
var cityZone, weatherAllowed bool
|
var cityZone, weatherAllowed bool
|
||||||
|
|
||||||
err := rows.Scan(&id, &name, &file, &description, &motd,
|
err := rows.Scan(&id, &name, &file, &description, &motd,
|
||||||
&minLevel, &maxLevel, &minVersion, &xpModifier,
|
&minLevel, &maxLevel, &minVersion, &xpModifier,
|
||||||
&cityZone, &weatherAllowed,
|
&cityZone, &weatherAllowed,
|
||||||
@ -203,7 +202,7 @@ func (d *Database) GetZones() ([]map[string]interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
zone["id"] = id
|
zone["id"] = id
|
||||||
zone["name"] = name
|
zone["name"] = name
|
||||||
zone["file"] = file
|
zone["file"] = file
|
||||||
@ -219,9 +218,9 @@ func (d *Database) GetZones() ([]map[string]interface{}, error) {
|
|||||||
zone["safe_y"] = safeY
|
zone["safe_y"] = safeY
|
||||||
zone["safe_z"] = safeZ
|
zone["safe_z"] = safeZ
|
||||||
zone["safe_heading"] = safeHeading
|
zone["safe_heading"] = safeHeading
|
||||||
|
|
||||||
zones = append(zones, zone)
|
zones = append(zones, zone)
|
||||||
}
|
}
|
||||||
|
|
||||||
return zones, rows.Err()
|
return zones, rows.Err()
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ type PacketData struct {
|
|||||||
// PacketContext provides context for packet handlers
|
// PacketContext provides context for packet handlers
|
||||||
type PacketContext struct {
|
type PacketContext struct {
|
||||||
Client ClientInterface // Client connection interface
|
Client ClientInterface // Client connection interface
|
||||||
World WorldInterface // World server interface
|
World WorldInterface // World server interface
|
||||||
Database DatabaseInterface // Database interface
|
Database DatabaseInterface // Database interface
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,8 +43,8 @@ type WorldInterface interface {
|
|||||||
|
|
||||||
// DatabaseInterface defines database operations needed by packet handlers
|
// DatabaseInterface defines database operations needed by packet handlers
|
||||||
type DatabaseInterface interface {
|
type DatabaseInterface interface {
|
||||||
GetCharacter(characterID int32) (map[string]interface{}, error)
|
GetCharacter(characterID int32) (map[string]any, error)
|
||||||
SaveCharacter(characterID int32, data map[string]interface{}) error
|
SaveCharacter(characterID int32, data map[string]any) error
|
||||||
// Add more database methods as needed
|
// Add more database methods as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ func NewPacketHandlerRegistry() *PacketHandlerRegistry {
|
|||||||
func (phr *PacketHandlerRegistry) RegisterHandler(opcode InternalOpcode, handler PacketHandlerFunc) {
|
func (phr *PacketHandlerRegistry) RegisterHandler(opcode InternalOpcode, handler PacketHandlerFunc) {
|
||||||
phr.mutex.Lock()
|
phr.mutex.Lock()
|
||||||
defer phr.mutex.Unlock()
|
defer phr.mutex.Unlock()
|
||||||
|
|
||||||
phr.handlers[opcode] = handler
|
phr.handlers[opcode] = handler
|
||||||
fmt.Printf("Registered handler for opcode %s (%d)\n", GetInternalOpcodeName(opcode), int(opcode))
|
fmt.Printf("Registered handler for opcode %s (%d)\n", GetInternalOpcodeName(opcode), int(opcode))
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ func (phr *PacketHandlerRegistry) RegisterHandler(opcode InternalOpcode, handler
|
|||||||
func (phr *PacketHandlerRegistry) UnregisterHandler(opcode InternalOpcode) {
|
func (phr *PacketHandlerRegistry) UnregisterHandler(opcode InternalOpcode) {
|
||||||
phr.mutex.Lock()
|
phr.mutex.Lock()
|
||||||
defer phr.mutex.Unlock()
|
defer phr.mutex.Unlock()
|
||||||
|
|
||||||
delete(phr.handlers, opcode)
|
delete(phr.handlers, opcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ func (phr *PacketHandlerRegistry) UnregisterHandler(opcode InternalOpcode) {
|
|||||||
func (phr *PacketHandlerRegistry) HasHandler(opcode InternalOpcode) bool {
|
func (phr *PacketHandlerRegistry) HasHandler(opcode InternalOpcode) bool {
|
||||||
phr.mutex.RLock()
|
phr.mutex.RLock()
|
||||||
defer phr.mutex.RUnlock()
|
defer phr.mutex.RUnlock()
|
||||||
|
|
||||||
_, exists := phr.handlers[opcode]
|
_, exists := phr.handlers[opcode]
|
||||||
return exists
|
return exists
|
||||||
}
|
}
|
||||||
@ -95,16 +95,16 @@ func (phr *PacketHandlerRegistry) HandlePacket(ctx *PacketContext, packet *Packe
|
|||||||
phr.mutex.RLock()
|
phr.mutex.RLock()
|
||||||
handler, exists := phr.handlers[packet.Opcode]
|
handler, exists := phr.handlers[packet.Opcode]
|
||||||
phr.mutex.RUnlock()
|
phr.mutex.RUnlock()
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
// No handler registered - this is not necessarily an error
|
// No handler registered - this is not necessarily an error
|
||||||
fmt.Printf("No handler registered for opcode %s (%d) from client %s\n",
|
fmt.Printf("No handler registered for opcode %s (%d) from client %s\n",
|
||||||
GetInternalOpcodeName(packet.Opcode),
|
GetInternalOpcodeName(packet.Opcode),
|
||||||
int(packet.Opcode),
|
int(packet.Opcode),
|
||||||
ctx.Client.GetCharacterName())
|
ctx.Client.GetCharacterName())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the handler
|
// Call the handler
|
||||||
return handler(ctx, packet)
|
return handler(ctx, packet)
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ func (phr *PacketHandlerRegistry) HandlePacket(ctx *PacketContext, packet *Packe
|
|||||||
func (phr *PacketHandlerRegistry) GetHandlerCount() int {
|
func (phr *PacketHandlerRegistry) GetHandlerCount() int {
|
||||||
phr.mutex.RLock()
|
phr.mutex.RLock()
|
||||||
defer phr.mutex.RUnlock()
|
defer phr.mutex.RUnlock()
|
||||||
|
|
||||||
return len(phr.handlers)
|
return len(phr.handlers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,18 +121,18 @@ func (phr *PacketHandlerRegistry) GetHandlerCount() int {
|
|||||||
func (phr *PacketHandlerRegistry) GetRegisteredOpcodes() []InternalOpcode {
|
func (phr *PacketHandlerRegistry) GetRegisteredOpcodes() []InternalOpcode {
|
||||||
phr.mutex.RLock()
|
phr.mutex.RLock()
|
||||||
defer phr.mutex.RUnlock()
|
defer phr.mutex.RUnlock()
|
||||||
|
|
||||||
opcodes := make([]InternalOpcode, 0, len(phr.handlers))
|
opcodes := make([]InternalOpcode, 0, len(phr.handlers))
|
||||||
for opcode := range phr.handlers {
|
for opcode := range phr.handlers {
|
||||||
opcodes = append(opcodes, opcode)
|
opcodes = append(opcodes, opcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return opcodes
|
return opcodes
|
||||||
}
|
}
|
||||||
|
|
||||||
// PacketProcessor combines opcode management and handler dispatch
|
// PacketProcessor combines opcode management and handler dispatch
|
||||||
type PacketProcessor struct {
|
type PacketProcessor struct {
|
||||||
opcodeManager *OpcodeManager
|
opcodeManager *OpcodeManager
|
||||||
handlerRegistry *PacketHandlerRegistry
|
handlerRegistry *PacketHandlerRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,17 +149,17 @@ func (pp *PacketProcessor) ProcessRawPacket(ctx *PacketContext, rawData []byte,
|
|||||||
if len(rawData) < 2 {
|
if len(rawData) < 2 {
|
||||||
return fmt.Errorf("packet too short: %d bytes", len(rawData))
|
return fmt.Errorf("packet too short: %d bytes", len(rawData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert client opcode to internal opcode
|
// Convert client opcode to internal opcode
|
||||||
clientVersion := ctx.Client.GetClientVersion()
|
clientVersion := ctx.Client.GetClientVersion()
|
||||||
internalOpcode := pp.opcodeManager.ClientOpcodeToInternal(clientVersion, clientOpcode)
|
internalOpcode := pp.opcodeManager.ClientOpcodeToInternal(clientVersion, clientOpcode)
|
||||||
|
|
||||||
if internalOpcode == OP_Unknown {
|
if internalOpcode == OP_Unknown {
|
||||||
fmt.Printf("Unknown opcode 0x%04X from client version %d (client: %s)\n",
|
fmt.Printf("Unknown opcode 0x%04X from client version %d (client: %s)\n",
|
||||||
clientOpcode, clientVersion, ctx.Client.GetCharacterName())
|
clientOpcode, clientVersion, ctx.Client.GetCharacterName())
|
||||||
return nil // Don't treat unknown opcodes as errors
|
return nil // Don't treat unknown opcodes as errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create packet data structure
|
// Create packet data structure
|
||||||
packet := &PacketData{
|
packet := &PacketData{
|
||||||
Opcode: internalOpcode,
|
Opcode: internalOpcode,
|
||||||
@ -168,7 +168,7 @@ func (pp *PacketProcessor) ProcessRawPacket(ctx *PacketContext, rawData []byte,
|
|||||||
Data: rawData,
|
Data: rawData,
|
||||||
Size: int32(len(rawData)),
|
Size: int32(len(rawData)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch to handler
|
// Dispatch to handler
|
||||||
return pp.handlerRegistry.HandlePacket(ctx, packet)
|
return pp.handlerRegistry.HandlePacket(ctx, packet)
|
||||||
}
|
}
|
||||||
@ -188,11 +188,11 @@ func (pp *PacketProcessor) GetStats() (int, int, []int32) {
|
|||||||
handlerCount := pp.handlerRegistry.GetHandlerCount()
|
handlerCount := pp.handlerRegistry.GetHandlerCount()
|
||||||
supportedVersions := pp.opcodeManager.GetSupportedVersions()
|
supportedVersions := pp.opcodeManager.GetSupportedVersions()
|
||||||
totalOpcodes := 0
|
totalOpcodes := 0
|
||||||
|
|
||||||
for _, version := range supportedVersions {
|
for _, version := range supportedVersions {
|
||||||
totalOpcodes += pp.opcodeManager.GetOpcodeCount(version)
|
totalOpcodes += pp.opcodeManager.GetOpcodeCount(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
return handlerCount, totalOpcodes, supportedVersions
|
return handlerCount, totalOpcodes, supportedVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,4 +219,4 @@ func ProcessGlobalPacket(ctx *PacketContext, rawData []byte, clientOpcode uint16
|
|||||||
// LoadGlobalOpcodeMappings loads opcode mappings with the global processor
|
// LoadGlobalOpcodeMappings loads opcode mappings with the global processor
|
||||||
func LoadGlobalOpcodeMappings(clientVersion int32, opcodeMap map[string]uint16) error {
|
func LoadGlobalOpcodeMappings(clientVersion int32, opcodeMap map[string]uint16) error {
|
||||||
return globalPacketProcessor.LoadOpcodeMap(clientVersion, opcodeMap)
|
return globalPacketProcessor.LoadOpcodeMap(clientVersion, opcodeMap)
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ All server data is persisted to SQLite:
|
|||||||
func (d *Database) LoadRules() (map[string]map[string]string, error)
|
func (d *Database) LoadRules() (map[string]map[string]string, error)
|
||||||
|
|
||||||
// Zone management
|
// Zone management
|
||||||
func (d *Database) GetZones() ([]map[string]interface{}, error)
|
func (d *Database) GetZones() ([]map[string]any, error)
|
||||||
|
|
||||||
// Character persistence (planned)
|
// Character persistence (planned)
|
||||||
func (d *Database) SaveCharacter(character *Character) error
|
func (d *Database) SaveCharacter(character *Character) error
|
||||||
@ -354,4 +354,4 @@ Built-in monitoring capabilities:
|
|||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
The EQ2Go World Server provides a solid foundation for the EverQuest II server emulator. It maintains compatibility with the original protocol while leveraging modern Go patterns for improved reliability, performance, and maintainability. The modular design allows for easy extension and integration with additional game systems as they are implemented.
|
The EQ2Go World Server provides a solid foundation for the EverQuest II server emulator. It maintains compatibility with the original protocol while leveraging modern Go patterns for improved reliability, performance, and maintainability. The modular design allows for easy extension and integration with additional game systems as they are implemented.
|
||||||
|
@ -15,19 +15,19 @@ const (
|
|||||||
EventDeathByPlayer
|
EventDeathByPlayer
|
||||||
EventDamageDealt
|
EventDamageDealt
|
||||||
EventHealingDone
|
EventHealingDone
|
||||||
|
|
||||||
// Quest events
|
// Quest events
|
||||||
EventQuestCompleted
|
EventQuestCompleted
|
||||||
EventQuestStep
|
EventQuestStep
|
||||||
EventQuestStarted
|
EventQuestStarted
|
||||||
EventQuestAbandoned
|
EventQuestAbandoned
|
||||||
|
|
||||||
// Skill events
|
// Skill events
|
||||||
EventSkillIncrease
|
EventSkillIncrease
|
||||||
EventSkillMastery
|
EventSkillMastery
|
||||||
EventSpellLearned
|
EventSpellLearned
|
||||||
EventSpellCast
|
EventSpellCast
|
||||||
|
|
||||||
// Item events
|
// Item events
|
||||||
EventItemDiscovered
|
EventItemDiscovered
|
||||||
EventItemCrafted
|
EventItemCrafted
|
||||||
@ -35,12 +35,12 @@ const (
|
|||||||
EventItemEquipped
|
EventItemEquipped
|
||||||
EventItemSold
|
EventItemSold
|
||||||
EventItemBought
|
EventItemBought
|
||||||
|
|
||||||
// Exploration events
|
// Exploration events
|
||||||
EventZoneDiscovered
|
EventZoneDiscovered
|
||||||
EventLocationDiscovered
|
EventLocationDiscovered
|
||||||
EventPOIDiscovered
|
EventPOIDiscovered
|
||||||
|
|
||||||
// Social events
|
// Social events
|
||||||
EventGuildJoin
|
EventGuildJoin
|
||||||
EventGuildLeave
|
EventGuildLeave
|
||||||
@ -48,27 +48,27 @@ const (
|
|||||||
EventGroupLeave
|
EventGroupLeave
|
||||||
EventFriendAdded
|
EventFriendAdded
|
||||||
EventPlayerTell
|
EventPlayerTell
|
||||||
|
|
||||||
// Harvesting/Crafting events
|
// Harvesting/Crafting events
|
||||||
EventHarvest
|
EventHarvest
|
||||||
EventRareHarvest
|
EventRareHarvest
|
||||||
EventCraftingSuccess
|
EventCraftingSuccess
|
||||||
EventCraftingFailure
|
EventCraftingFailure
|
||||||
EventRecipeDiscovered
|
EventRecipeDiscovered
|
||||||
|
|
||||||
// Level/Experience events
|
// Level/Experience events
|
||||||
EventLevelGain
|
EventLevelGain
|
||||||
EventAAPoint
|
EventAAPoint
|
||||||
EventExperienceGain
|
EventExperienceGain
|
||||||
EventStatusGain
|
EventStatusGain
|
||||||
|
|
||||||
// PvP events
|
// PvP events
|
||||||
EventPvPKill
|
EventPvPKill
|
||||||
EventPvPDeath
|
EventPvPDeath
|
||||||
EventPvPAssist
|
EventPvPAssist
|
||||||
EventArenaWin
|
EventArenaWin
|
||||||
EventArenaLoss
|
EventArenaLoss
|
||||||
|
|
||||||
// Special events
|
// Special events
|
||||||
EventHeroicOpportunity
|
EventHeroicOpportunity
|
||||||
EventRaidBoss
|
EventRaidBoss
|
||||||
@ -80,7 +80,7 @@ const (
|
|||||||
type AchievementEvent struct {
|
type AchievementEvent struct {
|
||||||
Type AchievementEventType
|
Type AchievementEventType
|
||||||
CharacterID int32
|
CharacterID int32
|
||||||
Data map[string]interface{}
|
Data map[string]any
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,13 +101,13 @@ func (aeh *AchievementEventHandler) ProcessEvent(event *AchievementEvent) error
|
|||||||
if event == nil {
|
if event == nil {
|
||||||
return fmt.Errorf("event cannot be nil")
|
return fmt.Errorf("event cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get player's achievement manager
|
// Get player's achievement manager
|
||||||
achievementMgr := aeh.world.GetAchievementManager()
|
achievementMgr := aeh.world.GetAchievementManager()
|
||||||
if achievementMgr == nil {
|
if achievementMgr == nil {
|
||||||
return fmt.Errorf("achievement manager not available")
|
return fmt.Errorf("achievement manager not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process different event types
|
// Process different event types
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case EventNPCKill:
|
case EventNPCKill:
|
||||||
@ -138,28 +138,28 @@ func (aeh *AchievementEventHandler) handleNPCKill(event *AchievementEvent, achie
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("npc_id not found in event data")
|
return fmt.Errorf("npc_id not found in event data")
|
||||||
}
|
}
|
||||||
|
|
||||||
level, ok := event.Data["level"].(int32)
|
level, ok := event.Data["level"].(int32)
|
||||||
if !ok {
|
if !ok {
|
||||||
level = 1 // Default level
|
level = 1 // Default level
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update generic kill count achievements
|
// Update generic kill count achievements
|
||||||
err := achievementMgr.UpdateProgress(event.CharacterID, 1, 1) // Achievement ID 1: "First Blood"
|
err := achievementMgr.UpdateProgress(event.CharacterID, 1, 1) // Achievement ID 1: "First Blood"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error updating kill achievement: %v\n", err)
|
fmt.Printf("Error updating kill achievement: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update level-specific kill achievements
|
// Update level-specific kill achievements
|
||||||
if level >= 10 {
|
if level >= 10 {
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 2, 1) // Achievement ID 2: "Veteran Hunter"
|
achievementMgr.UpdateProgress(event.CharacterID, 2, 1) // Achievement ID 2: "Veteran Hunter"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update specific NPC kill achievements (example)
|
// Update specific NPC kill achievements (example)
|
||||||
if npcID == 100 { // Boss NPC
|
if npcID == 100 { // Boss NPC
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 10, 1) // Achievement ID 10: "Boss Slayer"
|
achievementMgr.UpdateProgress(event.CharacterID, 10, 1) // Achievement ID 10: "Boss Slayer"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,18 +169,18 @@ func (aeh *AchievementEventHandler) handleQuestCompleted(event *AchievementEvent
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("quest_id not found in event data")
|
return fmt.Errorf("quest_id not found in event data")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update quest completion achievements
|
// Update quest completion achievements
|
||||||
err := achievementMgr.UpdateProgress(event.CharacterID, 20, 1) // Achievement ID 20: "Quest Master"
|
err := achievementMgr.UpdateProgress(event.CharacterID, 20, 1) // Achievement ID 20: "Quest Master"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error updating quest achievement: %v\n", err)
|
fmt.Printf("Error updating quest achievement: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update specific quest achievements
|
// Update specific quest achievements
|
||||||
if questID == 1000 { // Main story quest
|
if questID == 1000 { // Main story quest
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 21, 1) // Achievement ID 21: "Hero's Journey"
|
achievementMgr.UpdateProgress(event.CharacterID, 21, 1) // Achievement ID 21: "Hero's Journey"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +190,7 @@ func (aeh *AchievementEventHandler) handleLevelGain(event *AchievementEvent, ach
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("level not found in event data")
|
return fmt.Errorf("level not found in event data")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update level-based achievements
|
// Update level-based achievements
|
||||||
switch newLevel {
|
switch newLevel {
|
||||||
case 10:
|
case 10:
|
||||||
@ -202,10 +202,10 @@ func (aeh *AchievementEventHandler) handleLevelGain(event *AchievementEvent, ach
|
|||||||
case 90:
|
case 90:
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 33, 1) // Achievement ID 33: "Master Adventurer"
|
achievementMgr.UpdateProgress(event.CharacterID, 33, 1) // Achievement ID 33: "Master Adventurer"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update max level achievement
|
// Update max level achievement
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 34, uint32(newLevel)) // Achievement ID 34: "Level Up!"
|
achievementMgr.UpdateProgress(event.CharacterID, 34, uint32(newLevel)) // Achievement ID 34: "Level Up!"
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,12 +215,12 @@ func (aeh *AchievementEventHandler) handleSkillIncrease(event *AchievementEvent,
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("skill_id not found in event data")
|
return fmt.Errorf("skill_id not found in event data")
|
||||||
}
|
}
|
||||||
|
|
||||||
skillLevel, ok := event.Data["skill_level"].(int32)
|
skillLevel, ok := event.Data["skill_level"].(int32)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("skill_level not found in event data")
|
return fmt.Errorf("skill_level not found in event data")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update skill mastery achievements based on skill type
|
// Update skill mastery achievements based on skill type
|
||||||
switch skillID {
|
switch skillID {
|
||||||
case 1: // Melee skill
|
case 1: // Melee skill
|
||||||
@ -236,7 +236,7 @@ func (aeh *AchievementEventHandler) handleSkillIncrease(event *AchievementEvent,
|
|||||||
achievementMgr.UpdateProgress(event.CharacterID, 42, 1) // Achievement ID 42: "Master Craftsman"
|
achievementMgr.UpdateProgress(event.CharacterID, 42, 1) // Achievement ID 42: "Master Craftsman"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,15 +246,15 @@ func (aeh *AchievementEventHandler) handleItemDiscovered(event *AchievementEvent
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("item_id not found in event data")
|
return fmt.Errorf("item_id not found in event data")
|
||||||
}
|
}
|
||||||
|
|
||||||
rarity, ok := event.Data["rarity"].(string)
|
rarity, ok := event.Data["rarity"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
rarity = "common"
|
rarity = "common"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update item discovery achievements
|
// Update item discovery achievements
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 50, 1) // Achievement ID 50: "Treasure Hunter"
|
achievementMgr.UpdateProgress(event.CharacterID, 50, 1) // Achievement ID 50: "Treasure Hunter"
|
||||||
|
|
||||||
// Update rarity-specific achievements
|
// Update rarity-specific achievements
|
||||||
switch rarity {
|
switch rarity {
|
||||||
case "rare":
|
case "rare":
|
||||||
@ -264,12 +264,12 @@ func (aeh *AchievementEventHandler) handleItemDiscovered(event *AchievementEvent
|
|||||||
case "mythical":
|
case "mythical":
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 53, 1) // Achievement ID 53: "Myth Walker"
|
achievementMgr.UpdateProgress(event.CharacterID, 53, 1) // Achievement ID 53: "Myth Walker"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specific item achievements
|
// Specific item achievements
|
||||||
if itemID == 12345 { // Special artifact
|
if itemID == 12345 { // Special artifact
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 54, 1) // Achievement ID 54: "Ancient Artifact"
|
achievementMgr.UpdateProgress(event.CharacterID, 54, 1) // Achievement ID 54: "Ancient Artifact"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,10 +279,10 @@ func (aeh *AchievementEventHandler) handleZoneDiscovered(event *AchievementEvent
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("zone_id not found in event data")
|
return fmt.Errorf("zone_id not found in event data")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update exploration achievements
|
// Update exploration achievements
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 60, 1) // Achievement ID 60: "Explorer"
|
achievementMgr.UpdateProgress(event.CharacterID, 60, 1) // Achievement ID 60: "Explorer"
|
||||||
|
|
||||||
// Update specific zone achievements
|
// Update specific zone achievements
|
||||||
switch zoneID {
|
switch zoneID {
|
||||||
case 1: // Starting zone
|
case 1: // Starting zone
|
||||||
@ -290,7 +290,7 @@ func (aeh *AchievementEventHandler) handleZoneDiscovered(event *AchievementEvent
|
|||||||
case 100: // End game zone
|
case 100: // End game zone
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 62, 1) // Achievement ID 62: "Into the Unknown"
|
achievementMgr.UpdateProgress(event.CharacterID, 62, 1) // Achievement ID 62: "Into the Unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,12 +300,12 @@ func (aeh *AchievementEventHandler) handleHarvest(event *AchievementEvent, achie
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("resource_type not found in event data")
|
return fmt.Errorf("resource_type not found in event data")
|
||||||
}
|
}
|
||||||
|
|
||||||
isRare, _ := event.Data["is_rare"].(bool)
|
isRare, _ := event.Data["is_rare"].(bool)
|
||||||
|
|
||||||
// Update harvesting achievements
|
// Update harvesting achievements
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 70, 1) // Achievement ID 70: "Gatherer"
|
achievementMgr.UpdateProgress(event.CharacterID, 70, 1) // Achievement ID 70: "Gatherer"
|
||||||
|
|
||||||
// Update resource-specific achievements
|
// Update resource-specific achievements
|
||||||
switch resourceType {
|
switch resourceType {
|
||||||
case "ore":
|
case "ore":
|
||||||
@ -315,12 +315,12 @@ func (aeh *AchievementEventHandler) handleHarvest(event *AchievementEvent, achie
|
|||||||
case "fish":
|
case "fish":
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 73, 1) // Achievement ID 73: "Angler"
|
achievementMgr.UpdateProgress(event.CharacterID, 73, 1) // Achievement ID 73: "Angler"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update rare harvest achievement
|
// Update rare harvest achievement
|
||||||
if isRare {
|
if isRare {
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 74, 1) // Achievement ID 74: "Lucky Find"
|
achievementMgr.UpdateProgress(event.CharacterID, 74, 1) // Achievement ID 74: "Lucky Find"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,15 +330,15 @@ func (aeh *AchievementEventHandler) handlePvPKill(event *AchievementEvent, achie
|
|||||||
if !ok {
|
if !ok {
|
||||||
targetLevel = 1
|
targetLevel = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update PvP achievements
|
// Update PvP achievements
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 80, 1) // Achievement ID 80: "First Blood PvP"
|
achievementMgr.UpdateProgress(event.CharacterID, 80, 1) // Achievement ID 80: "First Blood PvP"
|
||||||
|
|
||||||
// Update level-based PvP achievements
|
// Update level-based PvP achievements
|
||||||
if targetLevel >= 50 {
|
if targetLevel >= 50 {
|
||||||
achievementMgr.UpdateProgress(event.CharacterID, 81, 1) // Achievement ID 81: "Veteran Slayer"
|
achievementMgr.UpdateProgress(event.CharacterID, 81, 1) // Achievement ID 81: "Veteran Slayer"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,27 +346,27 @@ func (aeh *AchievementEventHandler) handlePvPKill(event *AchievementEvent, achie
|
|||||||
func (aeh *AchievementEventHandler) handleGenericEvent(event *AchievementEvent, achievementMgr *AchievementManager) error {
|
func (aeh *AchievementEventHandler) handleGenericEvent(event *AchievementEvent, achievementMgr *AchievementManager) error {
|
||||||
// For events without specific handlers, attempt generic progress updates
|
// For events without specific handlers, attempt generic progress updates
|
||||||
// This allows for easy extension without requiring handler updates
|
// This allows for easy extension without requiring handler updates
|
||||||
|
|
||||||
// Log unhandled event types for debugging
|
// Log unhandled event types for debugging
|
||||||
fmt.Printf("Unhandled achievement event type: %d for character %d\n",
|
fmt.Printf("Unhandled achievement event type: %d for character %d\n",
|
||||||
int(event.Type), event.CharacterID)
|
int(event.Type), event.CharacterID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TriggerEvent is a convenience method for triggering achievement events
|
// TriggerEvent is a convenience method for triggering achievement events
|
||||||
func (w *World) TriggerAchievementEvent(eventType AchievementEventType, characterID int32, data map[string]interface{}) {
|
func (w *World) TriggerAchievementEvent(eventType AchievementEventType, characterID int32, data map[string]any) {
|
||||||
if w.achievementMgr == nil {
|
if w.achievementMgr == nil {
|
||||||
return // Achievement system not initialized
|
return // Achievement system not initialized
|
||||||
}
|
}
|
||||||
|
|
||||||
event := &AchievementEvent{
|
event := &AchievementEvent{
|
||||||
Type: eventType,
|
Type: eventType,
|
||||||
CharacterID: characterID,
|
CharacterID: characterID,
|
||||||
Data: data,
|
Data: data,
|
||||||
Timestamp: int64(w.worldTime.Year), // Use game time as timestamp
|
Timestamp: int64(w.worldTime.Year), // Use game time as timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := NewAchievementEventHandler(w)
|
handler := NewAchievementEventHandler(w)
|
||||||
go func() {
|
go func() {
|
||||||
if err := handler.ProcessEvent(event); err != nil {
|
if err := handler.ProcessEvent(event); err != nil {
|
||||||
@ -379,7 +379,7 @@ func (w *World) TriggerAchievementEvent(eventType AchievementEventType, characte
|
|||||||
|
|
||||||
// OnNPCKill triggers an NPC kill achievement event
|
// OnNPCKill triggers an NPC kill achievement event
|
||||||
func (w *World) OnNPCKill(characterID int32, npcID int32, npcLevel int32) {
|
func (w *World) OnNPCKill(characterID int32, npcID int32, npcLevel int32) {
|
||||||
w.TriggerAchievementEvent(EventNPCKill, characterID, map[string]interface{}{
|
w.TriggerAchievementEvent(EventNPCKill, characterID, map[string]any{
|
||||||
"npc_id": npcID,
|
"npc_id": npcID,
|
||||||
"level": npcLevel,
|
"level": npcLevel,
|
||||||
})
|
})
|
||||||
@ -387,21 +387,21 @@ func (w *World) OnNPCKill(characterID int32, npcID int32, npcLevel int32) {
|
|||||||
|
|
||||||
// OnQuestComplete triggers a quest completion achievement event
|
// OnQuestComplete triggers a quest completion achievement event
|
||||||
func (w *World) OnQuestComplete(characterID int32, questID int32) {
|
func (w *World) OnQuestComplete(characterID int32, questID int32) {
|
||||||
w.TriggerAchievementEvent(EventQuestCompleted, characterID, map[string]interface{}{
|
w.TriggerAchievementEvent(EventQuestCompleted, characterID, map[string]any{
|
||||||
"quest_id": questID,
|
"quest_id": questID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnLevelGain triggers a level gain achievement event
|
// OnLevelGain triggers a level gain achievement event
|
||||||
func (w *World) OnLevelGain(characterID int32, newLevel int32) {
|
func (w *World) OnLevelGain(characterID int32, newLevel int32) {
|
||||||
w.TriggerAchievementEvent(EventLevelGain, characterID, map[string]interface{}{
|
w.TriggerAchievementEvent(EventLevelGain, characterID, map[string]any{
|
||||||
"level": newLevel,
|
"level": newLevel,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnItemDiscovered triggers an item discovery achievement event
|
// OnItemDiscovered triggers an item discovery achievement event
|
||||||
func (w *World) OnItemDiscovered(characterID int32, itemID int32, rarity string) {
|
func (w *World) OnItemDiscovered(characterID int32, itemID int32, rarity string) {
|
||||||
w.TriggerAchievementEvent(EventItemDiscovered, characterID, map[string]interface{}{
|
w.TriggerAchievementEvent(EventItemDiscovered, characterID, map[string]any{
|
||||||
"item_id": itemID,
|
"item_id": itemID,
|
||||||
"rarity": rarity,
|
"rarity": rarity,
|
||||||
})
|
})
|
||||||
@ -409,7 +409,7 @@ func (w *World) OnItemDiscovered(characterID int32, itemID int32, rarity string)
|
|||||||
|
|
||||||
// OnZoneDiscovered triggers a zone discovery achievement event
|
// OnZoneDiscovered triggers a zone discovery achievement event
|
||||||
func (w *World) OnZoneDiscovered(characterID int32, zoneID int32) {
|
func (w *World) OnZoneDiscovered(characterID int32, zoneID int32) {
|
||||||
w.TriggerAchievementEvent(EventZoneDiscovered, characterID, map[string]interface{}{
|
w.TriggerAchievementEvent(EventZoneDiscovered, characterID, map[string]any{
|
||||||
"zone_id": zoneID,
|
"zone_id": zoneID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,19 @@ package world
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"eq2emu/internal/achievements"
|
"eq2emu/internal/achievements"
|
||||||
"eq2emu/internal/database"
|
"eq2emu/internal/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AchievementManager manages achievements for the world server
|
// AchievementManager manages achievements for the world server
|
||||||
type AchievementManager struct {
|
type AchievementManager struct {
|
||||||
masterList *achievements.MasterList
|
masterList *achievements.MasterList
|
||||||
playerManagers map[int32]*achievements.PlayerManager // CharacterID -> PlayerManager
|
playerManagers map[int32]*achievements.PlayerManager // CharacterID -> PlayerManager
|
||||||
database *database.Database
|
database *database.Database
|
||||||
world *World // Reference to world server for notifications
|
world *World // Reference to world server for notifications
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAchievementManager creates a new achievement manager
|
// NewAchievementManager creates a new achievement manager
|
||||||
@ -36,17 +36,17 @@ func (am *AchievementManager) SetWorld(world *World) {
|
|||||||
// LoadAchievements loads all achievements from database
|
// LoadAchievements loads all achievements from database
|
||||||
func (am *AchievementManager) LoadAchievements() error {
|
func (am *AchievementManager) LoadAchievements() error {
|
||||||
fmt.Println("Loading master achievement list...")
|
fmt.Println("Loading master achievement list...")
|
||||||
|
|
||||||
pool := am.database.GetPool()
|
pool := am.database.GetPool()
|
||||||
if pool == nil {
|
if pool == nil {
|
||||||
return fmt.Errorf("database pool is nil")
|
return fmt.Errorf("database pool is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := achievements.LoadAllAchievements(pool, am.masterList)
|
err := achievements.LoadAllAchievements(pool, am.masterList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load achievements: %w", err)
|
return fmt.Errorf("failed to load achievements: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Loaded %d achievements\n", am.masterList.Size())
|
fmt.Printf("Loaded %d achievements\n", am.masterList.Size())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -56,26 +56,26 @@ func (am *AchievementManager) GetPlayerManager(characterID int32) *achievements.
|
|||||||
am.mutex.RLock()
|
am.mutex.RLock()
|
||||||
playerMgr, exists := am.playerManagers[characterID]
|
playerMgr, exists := am.playerManagers[characterID]
|
||||||
am.mutex.RUnlock()
|
am.mutex.RUnlock()
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
return playerMgr
|
return playerMgr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new player manager and load data
|
// Create new player manager and load data
|
||||||
am.mutex.Lock()
|
am.mutex.Lock()
|
||||||
defer am.mutex.Unlock()
|
defer am.mutex.Unlock()
|
||||||
|
|
||||||
// Double-check after acquiring write lock
|
// Double-check after acquiring write lock
|
||||||
if playerMgr, exists := am.playerManagers[characterID]; exists {
|
if playerMgr, exists := am.playerManagers[characterID]; exists {
|
||||||
return playerMgr
|
return playerMgr
|
||||||
}
|
}
|
||||||
|
|
||||||
playerMgr = achievements.NewPlayerManager()
|
playerMgr = achievements.NewPlayerManager()
|
||||||
am.playerManagers[characterID] = playerMgr
|
am.playerManagers[characterID] = playerMgr
|
||||||
|
|
||||||
// Load player achievement data from database
|
// Load player achievement data from database
|
||||||
go am.loadPlayerAchievements(characterID, playerMgr)
|
go am.loadPlayerAchievements(characterID, playerMgr)
|
||||||
|
|
||||||
return playerMgr
|
return playerMgr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,13 +86,13 @@ func (am *AchievementManager) loadPlayerAchievements(characterID int32, playerMg
|
|||||||
fmt.Printf("Error: database pool is nil for character %d\n", characterID)
|
fmt.Printf("Error: database pool is nil for character %d\n", characterID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load player achievements
|
// Load player achievements
|
||||||
err := achievements.LoadPlayerAchievements(pool, uint32(characterID), playerMgr.Achievements)
|
err := achievements.LoadPlayerAchievements(pool, uint32(characterID), playerMgr.Achievements)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error loading achievements for character %d: %v\n", characterID, err)
|
fmt.Printf("Error loading achievements for character %d: %v\n", characterID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load player progress
|
// Load player progress
|
||||||
err = achievements.LoadPlayerAchievementUpdates(pool, uint32(characterID), playerMgr.Updates)
|
err = achievements.LoadPlayerAchievementUpdates(pool, uint32(characterID), playerMgr.Updates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -106,10 +106,10 @@ func (am *AchievementManager) UpdateProgress(characterID int32, achievementID ui
|
|||||||
if playerMgr == nil {
|
if playerMgr == nil {
|
||||||
return fmt.Errorf("failed to get player manager for character %d", characterID)
|
return fmt.Errorf("failed to get player manager for character %d", characterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update progress
|
// Update progress
|
||||||
playerMgr.Updates.UpdateProgress(achievementID, progress)
|
playerMgr.Updates.UpdateProgress(achievementID, progress)
|
||||||
|
|
||||||
// Check if achievement is completed
|
// Check if achievement is completed
|
||||||
achievement := am.masterList.GetAchievement(achievementID)
|
achievement := am.masterList.GetAchievement(achievementID)
|
||||||
if achievement != nil {
|
if achievement != nil {
|
||||||
@ -117,24 +117,24 @@ func (am *AchievementManager) UpdateProgress(characterID int32, achievementID ui
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to check requirements: %w", err)
|
return fmt.Errorf("failed to check requirements: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if completed && !playerMgr.Updates.IsCompleted(achievementID) {
|
if completed && !playerMgr.Updates.IsCompleted(achievementID) {
|
||||||
// Complete the achievement
|
// Complete the achievement
|
||||||
playerMgr.Updates.CompleteAchievement(achievementID)
|
playerMgr.Updates.CompleteAchievement(achievementID)
|
||||||
|
|
||||||
// Save progress to database
|
// Save progress to database
|
||||||
go am.savePlayerProgress(characterID, achievementID, playerMgr)
|
go am.savePlayerProgress(characterID, achievementID, playerMgr)
|
||||||
|
|
||||||
// Trigger achievement completion event
|
// Trigger achievement completion event
|
||||||
go am.onAchievementCompleted(characterID, achievement)
|
go am.onAchievementCompleted(characterID, achievement)
|
||||||
|
|
||||||
fmt.Printf("Character %d completed achievement: %s\n", characterID, achievement.Title)
|
fmt.Printf("Character %d completed achievement: %s\n", characterID, achievement.Title)
|
||||||
} else if progress > 0 {
|
} else if progress > 0 {
|
||||||
// Save progress update to database
|
// Save progress update to database
|
||||||
go am.savePlayerProgress(characterID, achievementID, playerMgr)
|
go am.savePlayerProgress(characterID, achievementID, playerMgr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,16 +144,16 @@ func (am *AchievementManager) savePlayerProgress(characterID int32, achievementI
|
|||||||
if update == nil {
|
if update == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pool := am.database.GetPool()
|
pool := am.database.GetPool()
|
||||||
if pool == nil {
|
if pool == nil {
|
||||||
fmt.Printf("Error: database pool is nil for character %d\n", characterID)
|
fmt.Printf("Error: database pool is nil for character %d\n", characterID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := achievements.SavePlayerAchievementUpdate(pool, uint32(characterID), update)
|
err := achievements.SavePlayerAchievementUpdate(pool, uint32(characterID), update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving achievement progress for character %d, achievement %d: %v\n",
|
fmt.Printf("Error saving achievement progress for character %d, achievement %d: %v\n",
|
||||||
characterID, achievementID, err)
|
characterID, achievementID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,12 +165,12 @@ func (am *AchievementManager) onAchievementCompleted(characterID int32, achievem
|
|||||||
// Increment player's achievement points
|
// Increment player's achievement points
|
||||||
fmt.Printf("Character %d earned %d achievement points\n", characterID, achievement.PointValue)
|
fmt.Printf("Character %d earned %d achievement points\n", characterID, achievement.PointValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process rewards
|
// Process rewards
|
||||||
for _, reward := range achievement.Rewards {
|
for _, reward := range achievement.Rewards {
|
||||||
am.processReward(characterID, reward)
|
am.processReward(characterID, reward)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify other systems about achievement completion
|
// Notify other systems about achievement completion
|
||||||
am.notifyAchievementCompleted(characterID, achievement.ID)
|
am.notifyAchievementCompleted(characterID, achievement.ID)
|
||||||
}
|
}
|
||||||
@ -231,7 +231,7 @@ func (am *AchievementManager) GetPlayerProgress(characterID int32, achievementID
|
|||||||
if playerMgr == nil {
|
if playerMgr == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return playerMgr.Updates.GetProgress(achievementID)
|
return playerMgr.Updates.GetProgress(achievementID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +241,7 @@ func (am *AchievementManager) IsPlayerCompleted(characterID int32, achievementID
|
|||||||
if playerMgr == nil {
|
if playerMgr == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return playerMgr.Updates.IsCompleted(achievementID)
|
return playerMgr.Updates.IsCompleted(achievementID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ func (am *AchievementManager) GetPlayerCompletedAchievements(characterID int32)
|
|||||||
if playerMgr == nil {
|
if playerMgr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return playerMgr.Updates.GetCompletedAchievements()
|
return playerMgr.Updates.GetCompletedAchievements()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +261,7 @@ func (am *AchievementManager) GetPlayerInProgressAchievements(characterID int32)
|
|||||||
if playerMgr == nil {
|
if playerMgr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return playerMgr.Updates.GetInProgressAchievements()
|
return playerMgr.Updates.GetInProgressAchievements()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,12 +271,12 @@ func (am *AchievementManager) GetCompletionPercentage(characterID int32, achieve
|
|||||||
if playerMgr == nil {
|
if playerMgr == nil {
|
||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
achievement := am.masterList.GetAchievement(achievementID)
|
achievement := am.masterList.GetAchievement(achievementID)
|
||||||
if achievement == nil {
|
if achievement == nil {
|
||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
return playerMgr.GetCompletionStatus(achievement)
|
return playerMgr.GetCompletionStatus(achievement)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,41 +284,41 @@ func (am *AchievementManager) GetCompletionPercentage(characterID int32, achieve
|
|||||||
func (am *AchievementManager) RemovePlayerManager(characterID int32) {
|
func (am *AchievementManager) RemovePlayerManager(characterID int32) {
|
||||||
am.mutex.Lock()
|
am.mutex.Lock()
|
||||||
defer am.mutex.Unlock()
|
defer am.mutex.Unlock()
|
||||||
|
|
||||||
delete(am.playerManagers, characterID)
|
delete(am.playerManagers, characterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns achievement system statistics
|
// GetStatistics returns achievement system statistics
|
||||||
func (am *AchievementManager) GetStatistics() map[string]interface{} {
|
func (am *AchievementManager) GetStatistics() map[string]any {
|
||||||
am.mutex.RLock()
|
am.mutex.RLock()
|
||||||
defer am.mutex.RUnlock()
|
defer am.mutex.RUnlock()
|
||||||
|
|
||||||
stats := map[string]interface{}{
|
stats := map[string]any{
|
||||||
"total_achievements": am.masterList.Size(),
|
"total_achievements": am.masterList.Size(),
|
||||||
"online_players": len(am.playerManagers),
|
"online_players": len(am.playerManagers),
|
||||||
"categories": am.masterList.GetCategories(),
|
"categories": am.masterList.GetCategories(),
|
||||||
"expansions": am.masterList.GetExpansions(),
|
"expansions": am.masterList.GetExpansions(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown gracefully shuts down the achievement manager
|
// Shutdown gracefully shuts down the achievement manager
|
||||||
func (am *AchievementManager) Shutdown() {
|
func (am *AchievementManager) Shutdown() {
|
||||||
fmt.Println("Shutting down achievement manager...")
|
fmt.Println("Shutting down achievement manager...")
|
||||||
|
|
||||||
am.mutex.Lock()
|
am.mutex.Lock()
|
||||||
defer am.mutex.Unlock()
|
defer am.mutex.Unlock()
|
||||||
|
|
||||||
// Save all player progress before shutdown
|
// Save all player progress before shutdown
|
||||||
for characterID, playerMgr := range am.playerManagers {
|
for characterID, playerMgr := range am.playerManagers {
|
||||||
for _, achievementID := range playerMgr.Updates.GetInProgressAchievements() {
|
for _, achievementID := range playerMgr.Updates.GetInProgressAchievements() {
|
||||||
am.savePlayerProgress(characterID, achievementID, playerMgr)
|
am.savePlayerProgress(characterID, achievementID, playerMgr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear player managers
|
// Clear player managers
|
||||||
am.playerManagers = make(map[int32]*achievements.PlayerManager)
|
am.playerManagers = make(map[int32]*achievements.PlayerManager)
|
||||||
|
|
||||||
fmt.Println("Achievement manager shutdown complete")
|
fmt.Println("Achievement manager shutdown complete")
|
||||||
}
|
}
|
||||||
|
@ -15,64 +15,64 @@ import (
|
|||||||
// Client represents a connected player client
|
// Client represents a connected player client
|
||||||
type Client struct {
|
type Client struct {
|
||||||
// Account information
|
// Account information
|
||||||
AccountID int32
|
AccountID int32
|
||||||
AccountName string
|
AccountName string
|
||||||
AdminLevel int
|
AdminLevel int
|
||||||
|
|
||||||
// Character information
|
// Character information
|
||||||
CharacterID int32
|
CharacterID int32
|
||||||
CharacterName string
|
CharacterName string
|
||||||
Player *entity.Entity
|
Player *entity.Entity
|
||||||
|
|
||||||
// Connection information
|
// Connection information
|
||||||
Connection interface{} // TODO: Will be *udp.Connection
|
Connection any // TODO: Will be *udp.Connection
|
||||||
IPAddress string
|
IPAddress string
|
||||||
ConnectedTime time.Time
|
ConnectedTime time.Time
|
||||||
LastActivity time.Time
|
LastActivity time.Time
|
||||||
ClientVersion int32 // EQ2 client version
|
ClientVersion int32 // EQ2 client version
|
||||||
|
|
||||||
// Zone information
|
// Zone information
|
||||||
CurrentZone *ZoneServer
|
CurrentZone *ZoneServer
|
||||||
ZoneID int32
|
ZoneID int32
|
||||||
|
|
||||||
// State flags
|
// State flags
|
||||||
IsConnected bool
|
IsConnected bool
|
||||||
IsLinkdead bool
|
IsLinkdead bool
|
||||||
IsAFK bool
|
IsAFK bool
|
||||||
IsAnonymous bool
|
IsAnonymous bool
|
||||||
IsLFG bool
|
IsLFG bool
|
||||||
|
|
||||||
// Chat state
|
// Chat state
|
||||||
LastTellFrom string
|
LastTellFrom string
|
||||||
IgnoreList map[string]bool
|
IgnoreList map[string]bool
|
||||||
|
|
||||||
// Group/Guild
|
// Group/Guild
|
||||||
GroupID int32
|
GroupID int32
|
||||||
GuildID int32
|
GuildID int32
|
||||||
|
|
||||||
// Pending operations
|
// Pending operations
|
||||||
PendingZone *ZoneChangeDetails
|
PendingZone *ZoneChangeDetails
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// ZoneChangeDetails holds information about a zone change
|
// ZoneChangeDetails holds information about a zone change
|
||||||
type ZoneChangeDetails struct {
|
type ZoneChangeDetails struct {
|
||||||
ZoneID int32
|
ZoneID int32
|
||||||
InstanceID int32
|
InstanceID int32
|
||||||
X float32
|
X float32
|
||||||
Y float32
|
Y float32
|
||||||
Z float32
|
Z float32
|
||||||
Heading float32
|
Heading float32
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientList manages all connected clients
|
// ClientList manages all connected clients
|
||||||
type ClientList struct {
|
type ClientList struct {
|
||||||
clients map[int32]*Client // CharacterID -> Client
|
clients map[int32]*Client // CharacterID -> Client
|
||||||
clientsByName map[string]*Client // Lowercase name -> Client
|
clientsByName map[string]*Client // Lowercase name -> Client
|
||||||
clientsByAcct map[int32][]*Client // AccountID -> Clients
|
clientsByAcct map[int32][]*Client // AccountID -> Clients
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientList creates a new client list
|
// NewClientList creates a new client list
|
||||||
@ -88,22 +88,22 @@ func NewClientList() *ClientList {
|
|||||||
func (cl *ClientList) Add(client *Client) error {
|
func (cl *ClientList) Add(client *Client) error {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
defer cl.mutex.Unlock()
|
||||||
|
|
||||||
if _, exists := cl.clients[client.CharacterID]; exists {
|
if _, exists := cl.clients[client.CharacterID]; exists {
|
||||||
return fmt.Errorf("client with character ID %d already exists", client.CharacterID)
|
return fmt.Errorf("client with character ID %d already exists", client.CharacterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to maps
|
// Add to maps
|
||||||
cl.clients[client.CharacterID] = client
|
cl.clients[client.CharacterID] = client
|
||||||
cl.clientsByName[strings.ToLower(client.CharacterName)] = client
|
cl.clientsByName[strings.ToLower(client.CharacterName)] = client
|
||||||
|
|
||||||
// Add to account map
|
// Add to account map
|
||||||
cl.clientsByAcct[client.AccountID] = append(cl.clientsByAcct[client.AccountID], client)
|
cl.clientsByAcct[client.AccountID] = append(cl.clientsByAcct[client.AccountID], client)
|
||||||
|
|
||||||
client.ConnectedTime = time.Now()
|
client.ConnectedTime = time.Now()
|
||||||
client.LastActivity = time.Now()
|
client.LastActivity = time.Now()
|
||||||
client.IsConnected = true
|
client.IsConnected = true
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,16 +111,16 @@ func (cl *ClientList) Add(client *Client) error {
|
|||||||
func (cl *ClientList) Remove(characterID int32) {
|
func (cl *ClientList) Remove(characterID int32) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
defer cl.mutex.Unlock()
|
||||||
|
|
||||||
client, exists := cl.clients[characterID]
|
client, exists := cl.clients[characterID]
|
||||||
if !exists {
|
if !exists {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from maps
|
// Remove from maps
|
||||||
delete(cl.clients, characterID)
|
delete(cl.clients, characterID)
|
||||||
delete(cl.clientsByName, strings.ToLower(client.CharacterName))
|
delete(cl.clientsByName, strings.ToLower(client.CharacterName))
|
||||||
|
|
||||||
// Remove from account map
|
// Remove from account map
|
||||||
if clients, ok := cl.clientsByAcct[client.AccountID]; ok {
|
if clients, ok := cl.clientsByAcct[client.AccountID]; ok {
|
||||||
newClients := make([]*Client, 0, len(clients)-1)
|
newClients := make([]*Client, 0, len(clients)-1)
|
||||||
@ -141,7 +141,7 @@ func (cl *ClientList) Remove(characterID int32) {
|
|||||||
func (cl *ClientList) GetByCharacterID(characterID int32) *Client {
|
func (cl *ClientList) GetByCharacterID(characterID int32) *Client {
|
||||||
cl.mutex.RLock()
|
cl.mutex.RLock()
|
||||||
defer cl.mutex.RUnlock()
|
defer cl.mutex.RUnlock()
|
||||||
|
|
||||||
return cl.clients[characterID]
|
return cl.clients[characterID]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ func (cl *ClientList) GetByCharacterID(characterID int32) *Client {
|
|||||||
func (cl *ClientList) GetByCharacterName(name string) *Client {
|
func (cl *ClientList) GetByCharacterName(name string) *Client {
|
||||||
cl.mutex.RLock()
|
cl.mutex.RLock()
|
||||||
defer cl.mutex.RUnlock()
|
defer cl.mutex.RUnlock()
|
||||||
|
|
||||||
return cl.clientsByName[strings.ToLower(name)]
|
return cl.clientsByName[strings.ToLower(name)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ func (cl *ClientList) GetByCharacterName(name string) *Client {
|
|||||||
func (cl *ClientList) GetByAccountID(accountID int32) []*Client {
|
func (cl *ClientList) GetByAccountID(accountID int32) []*Client {
|
||||||
cl.mutex.RLock()
|
cl.mutex.RLock()
|
||||||
defer cl.mutex.RUnlock()
|
defer cl.mutex.RUnlock()
|
||||||
|
|
||||||
clients := cl.clientsByAcct[accountID]
|
clients := cl.clientsByAcct[accountID]
|
||||||
result := make([]*Client, len(clients))
|
result := make([]*Client, len(clients))
|
||||||
copy(result, clients)
|
copy(result, clients)
|
||||||
@ -168,7 +168,7 @@ func (cl *ClientList) GetByAccountID(accountID int32) []*Client {
|
|||||||
func (cl *ClientList) Count() int32 {
|
func (cl *ClientList) Count() int32 {
|
||||||
cl.mutex.RLock()
|
cl.mutex.RLock()
|
||||||
defer cl.mutex.RUnlock()
|
defer cl.mutex.RUnlock()
|
||||||
|
|
||||||
return int32(len(cl.clients))
|
return int32(len(cl.clients))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ func (cl *ClientList) Count() int32 {
|
|||||||
func (cl *ClientList) GetAll() []*Client {
|
func (cl *ClientList) GetAll() []*Client {
|
||||||
cl.mutex.RLock()
|
cl.mutex.RLock()
|
||||||
defer cl.mutex.RUnlock()
|
defer cl.mutex.RUnlock()
|
||||||
|
|
||||||
result := make([]*Client, 0, len(cl.clients))
|
result := make([]*Client, 0, len(cl.clients))
|
||||||
for _, client := range cl.clients {
|
for _, client := range cl.clients {
|
||||||
result = append(result, client)
|
result = append(result, client)
|
||||||
@ -187,7 +187,7 @@ func (cl *ClientList) GetAll() []*Client {
|
|||||||
// ProcessAll processes all clients
|
// ProcessAll processes all clients
|
||||||
func (cl *ClientList) ProcessAll() {
|
func (cl *ClientList) ProcessAll() {
|
||||||
clients := cl.GetAll()
|
clients := cl.GetAll()
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
client.Process(now)
|
client.Process(now)
|
||||||
@ -197,7 +197,7 @@ func (cl *ClientList) ProcessAll() {
|
|||||||
// DisconnectAll disconnects all clients
|
// DisconnectAll disconnects all clients
|
||||||
func (cl *ClientList) DisconnectAll(reason string) {
|
func (cl *ClientList) DisconnectAll(reason string) {
|
||||||
clients := cl.GetAll()
|
clients := cl.GetAll()
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
client.DisconnectWithReason(reason)
|
client.DisconnectWithReason(reason)
|
||||||
}
|
}
|
||||||
@ -206,7 +206,7 @@ func (cl *ClientList) DisconnectAll(reason string) {
|
|||||||
// BroadcastMessage sends a message to all clients
|
// BroadcastMessage sends a message to all clients
|
||||||
func (cl *ClientList) BroadcastMessage(message string) {
|
func (cl *ClientList) BroadcastMessage(message string) {
|
||||||
clients := cl.GetAll()
|
clients := cl.GetAll()
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
client.SendSimpleMessage(message)
|
client.SendSimpleMessage(message)
|
||||||
}
|
}
|
||||||
@ -216,24 +216,24 @@ func (cl *ClientList) BroadcastMessage(message string) {
|
|||||||
func (c *Client) Process(now time.Time) {
|
func (c *Client) Process(now time.Time) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
if !c.IsConnected {
|
if !c.IsConnected {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for linkdead timeout
|
// Check for linkdead timeout
|
||||||
if now.Sub(c.LastActivity) > 5*time.Minute {
|
if now.Sub(c.LastActivity) > 5*time.Minute {
|
||||||
if !c.IsLinkdead {
|
if !c.IsLinkdead {
|
||||||
c.IsLinkdead = true
|
c.IsLinkdead = true
|
||||||
fmt.Printf("Client %s has gone linkdead\n", c.CharacterName)
|
fmt.Printf("Client %s has gone linkdead\n", c.CharacterName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect after 10 minutes
|
// Disconnect after 10 minutes
|
||||||
if now.Sub(c.LastActivity) > 10*time.Minute {
|
if now.Sub(c.LastActivity) > 10*time.Minute {
|
||||||
c.DisconnectWithReason("Linkdead timeout")
|
c.DisconnectWithReason("Linkdead timeout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process pending zone change
|
// Process pending zone change
|
||||||
if c.PendingZone != nil {
|
if c.PendingZone != nil {
|
||||||
// TODO: Implement zone change
|
// TODO: Implement zone change
|
||||||
@ -245,22 +245,22 @@ func (c *Client) Process(now time.Time) {
|
|||||||
func (c *Client) DisconnectWithReason(reason string) {
|
func (c *Client) DisconnectWithReason(reason string) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
if !c.IsConnected {
|
if !c.IsConnected {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Disconnecting client %s: %s\n", c.CharacterName, reason)
|
fmt.Printf("Disconnecting client %s: %s\n", c.CharacterName, reason)
|
||||||
|
|
||||||
// Remove from current zone
|
// Remove from current zone
|
||||||
if c.CurrentZone != nil {
|
if c.CurrentZone != nil {
|
||||||
c.CurrentZone.RemoveClient(c.CharacterID)
|
c.CurrentZone.RemoveClient(c.CharacterID)
|
||||||
c.CurrentZone = nil
|
c.CurrentZone = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Save character data
|
// TODO: Save character data
|
||||||
// TODO: Close connection
|
// TODO: Close connection
|
||||||
|
|
||||||
c.IsConnected = false
|
c.IsConnected = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +274,7 @@ func (c *Client) SendSimpleMessage(message string) {
|
|||||||
func (c *Client) Zone(details *ZoneChangeDetails) {
|
func (c *Client) Zone(details *ZoneChangeDetails) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
c.PendingZone = details
|
c.PendingZone = details
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,7 +282,7 @@ func (c *Client) Zone(details *ZoneChangeDetails) {
|
|||||||
func (c *Client) UpdateActivity() {
|
func (c *Client) UpdateActivity() {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
c.LastActivity = time.Now()
|
c.LastActivity = time.Now()
|
||||||
if c.IsLinkdead {
|
if c.IsLinkdead {
|
||||||
c.IsLinkdead = false
|
c.IsLinkdead = false
|
||||||
@ -411,7 +411,7 @@ func (c *Client) SetClientVersion(version int32) {
|
|||||||
func (c *Client) ProcessPacket(world *World, rawData []byte, clientOpcode uint16) error {
|
func (c *Client) ProcessPacket(world *World, rawData []byte, clientOpcode uint16) error {
|
||||||
// Create packet context
|
// Create packet context
|
||||||
ctx := world.CreatePacketContext(c)
|
ctx := world.CreatePacketContext(c)
|
||||||
|
|
||||||
// Process the packet through the global packet processor
|
// Process the packet through the global packet processor
|
||||||
return packets.ProcessGlobalPacket(ctx, rawData, clientOpcode)
|
return packets.ProcessGlobalPacket(ctx, rawData, clientOpcode)
|
||||||
}
|
}
|
||||||
|
@ -10,39 +10,39 @@ import (
|
|||||||
|
|
||||||
// ItemManager manages items for the world server
|
// ItemManager manages items for the world server
|
||||||
type ItemManager struct {
|
type ItemManager struct {
|
||||||
masterItemList *items.MasterItemList
|
masterItemList *items.MasterItemList
|
||||||
itemSystemAdapter *items.ItemSystemAdapter
|
itemSystemAdapter *items.ItemSystemAdapter
|
||||||
database *database.Database
|
database *database.Database
|
||||||
world *World // Reference to world server
|
world *World // Reference to world server
|
||||||
|
|
||||||
// World-specific item tracking
|
// World-specific item tracking
|
||||||
playerInventories map[uint32]*items.PlayerItemList // Player ID -> Inventory
|
playerInventories map[uint32]*items.PlayerItemList // Player ID -> Inventory
|
||||||
playerEquipment map[uint32]*items.EquipmentItemList // Player ID -> Equipment
|
playerEquipment map[uint32]*items.EquipmentItemList // Player ID -> Equipment
|
||||||
worldDrops map[int32][]*items.Item // Zone ID -> Ground items
|
worldDrops map[int32][]*items.Item // Zone ID -> Ground items
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewItemManager creates a new item manager for the world server
|
// NewItemManager creates a new item manager for the world server
|
||||||
func NewItemManager(db *database.Database) *ItemManager {
|
func NewItemManager(db *database.Database) *ItemManager {
|
||||||
// Create master item list
|
// Create master item list
|
||||||
masterList := items.NewMasterItemList()
|
masterList := items.NewMasterItemList()
|
||||||
|
|
||||||
// Create adapters for the item system
|
// Create adapters for the item system
|
||||||
dbAdapter := &WorldItemDatabaseAdapter{db: db}
|
dbAdapter := &WorldItemDatabaseAdapter{db: db}
|
||||||
playerAdapter := &WorldItemPlayerAdapter{}
|
playerAdapter := &WorldItemPlayerAdapter{}
|
||||||
packetAdapter := &WorldItemPacketAdapter{}
|
packetAdapter := &WorldItemPacketAdapter{}
|
||||||
ruleAdapter := &WorldItemRuleAdapter{}
|
ruleAdapter := &WorldItemRuleAdapter{}
|
||||||
|
|
||||||
// Create mock adapters for optional dependencies
|
// Create mock adapters for optional dependencies
|
||||||
questAdapter := &MockItemQuestAdapter{}
|
questAdapter := &MockItemQuestAdapter{}
|
||||||
brokerAdapter := &MockItemBrokerAdapter{}
|
brokerAdapter := &MockItemBrokerAdapter{}
|
||||||
craftingAdapter := &MockItemCraftingAdapter{}
|
craftingAdapter := &MockItemCraftingAdapter{}
|
||||||
housingAdapter := &MockItemHousingAdapter{}
|
housingAdapter := &MockItemHousingAdapter{}
|
||||||
|
|
||||||
// Create loot manager adapter
|
// Create loot manager adapter
|
||||||
lootAdapter := &WorldItemLootAdapter{}
|
lootAdapter := &WorldItemLootAdapter{}
|
||||||
|
|
||||||
// Create item system adapter
|
// Create item system adapter
|
||||||
systemAdapter := items.NewItemSystemAdapter(
|
systemAdapter := items.NewItemSystemAdapter(
|
||||||
masterList,
|
masterList,
|
||||||
@ -57,11 +57,11 @@ func NewItemManager(db *database.Database) *ItemManager {
|
|||||||
housingAdapter,
|
housingAdapter,
|
||||||
lootAdapter,
|
lootAdapter,
|
||||||
)
|
)
|
||||||
|
|
||||||
return &ItemManager{
|
return &ItemManager{
|
||||||
masterItemList: masterList,
|
masterItemList: masterList,
|
||||||
itemSystemAdapter: systemAdapter,
|
itemSystemAdapter: systemAdapter,
|
||||||
database: db,
|
database: db,
|
||||||
playerInventories: make(map[uint32]*items.PlayerItemList),
|
playerInventories: make(map[uint32]*items.PlayerItemList),
|
||||||
playerEquipment: make(map[uint32]*items.EquipmentItemList),
|
playerEquipment: make(map[uint32]*items.EquipmentItemList),
|
||||||
worldDrops: make(map[int32][]*items.Item),
|
worldDrops: make(map[int32][]*items.Item),
|
||||||
@ -76,13 +76,13 @@ func (im *ItemManager) SetWorld(world *World) {
|
|||||||
// LoadItems loads all item templates and data from database
|
// LoadItems loads all item templates and data from database
|
||||||
func (im *ItemManager) LoadItems() error {
|
func (im *ItemManager) LoadItems() error {
|
||||||
fmt.Println("Loading item data...")
|
fmt.Println("Loading item data...")
|
||||||
|
|
||||||
// Initialize the item system adapter
|
// Initialize the item system adapter
|
||||||
err := im.itemSystemAdapter.Initialize()
|
err := im.itemSystemAdapter.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize item system: %w", err)
|
return fmt.Errorf("failed to initialize item system: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := im.masterItemList.GetStats()
|
stats := im.masterItemList.GetStats()
|
||||||
fmt.Printf("Loaded %d item templates\n", stats.TotalItems)
|
fmt.Printf("Loaded %d item templates\n", stats.TotalItems)
|
||||||
return nil
|
return nil
|
||||||
@ -92,7 +92,7 @@ func (im *ItemManager) LoadItems() error {
|
|||||||
func (im *ItemManager) GetPlayerInventory(playerID uint32) (*items.PlayerItemList, error) {
|
func (im *ItemManager) GetPlayerInventory(playerID uint32) (*items.PlayerItemList, error) {
|
||||||
im.mutex.RLock()
|
im.mutex.RLock()
|
||||||
defer im.mutex.RUnlock()
|
defer im.mutex.RUnlock()
|
||||||
|
|
||||||
return im.itemSystemAdapter.GetPlayerInventory(playerID)
|
return im.itemSystemAdapter.GetPlayerInventory(playerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ func (im *ItemManager) GetPlayerInventory(playerID uint32) (*items.PlayerItemLis
|
|||||||
func (im *ItemManager) GetPlayerEquipment(playerID uint32, appearanceType int8) (*items.EquipmentItemList, error) {
|
func (im *ItemManager) GetPlayerEquipment(playerID uint32, appearanceType int8) (*items.EquipmentItemList, error) {
|
||||||
im.mutex.RLock()
|
im.mutex.RLock()
|
||||||
defer im.mutex.RUnlock()
|
defer im.mutex.RUnlock()
|
||||||
|
|
||||||
return im.itemSystemAdapter.GetPlayerEquipment(playerID, appearanceType)
|
return im.itemSystemAdapter.GetPlayerEquipment(playerID, appearanceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,26 +133,26 @@ func (im *ItemManager) MoveItem(playerID uint32, fromBagID int32, fromSlot int16
|
|||||||
func (im *ItemManager) CreateWorldDrop(itemID int32, quantity int16, x, y, z float32, zoneID int32) error {
|
func (im *ItemManager) CreateWorldDrop(itemID int32, quantity int16, x, y, z float32, zoneID int32) error {
|
||||||
im.mutex.Lock()
|
im.mutex.Lock()
|
||||||
defer im.mutex.Unlock()
|
defer im.mutex.Unlock()
|
||||||
|
|
||||||
// Get item template
|
// Get item template
|
||||||
itemTemplate := im.masterItemList.GetItem(itemID)
|
itemTemplate := im.masterItemList.GetItem(itemID)
|
||||||
if itemTemplate == nil {
|
if itemTemplate == nil {
|
||||||
return fmt.Errorf("item template not found: %d", itemID)
|
return fmt.Errorf("item template not found: %d", itemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create item instance
|
// Create item instance
|
||||||
item := items.NewItemFromTemplate(itemTemplate)
|
item := items.NewItemFromTemplate(itemTemplate)
|
||||||
item.Details.Count = quantity
|
item.Details.Count = quantity
|
||||||
|
|
||||||
// Set position data (would normally be in a WorldItem wrapper)
|
// Set position data (would normally be in a WorldItem wrapper)
|
||||||
// TODO: Create WorldItem wrapper with position data
|
// TODO: Create WorldItem wrapper with position data
|
||||||
|
|
||||||
// Add to world drops tracking
|
// Add to world drops tracking
|
||||||
im.worldDrops[zoneID] = append(im.worldDrops[zoneID], item)
|
im.worldDrops[zoneID] = append(im.worldDrops[zoneID], item)
|
||||||
|
|
||||||
fmt.Printf("Created world drop: %s x%d at (%.2f, %.2f, %.2f) in zone %d\n",
|
fmt.Printf("Created world drop: %s x%d at (%.2f, %.2f, %.2f) in zone %d\n",
|
||||||
item.Name, quantity, x, y, z, zoneID)
|
item.Name, quantity, x, y, z, zoneID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,14 +160,14 @@ func (im *ItemManager) CreateWorldDrop(itemID int32, quantity int16, x, y, z flo
|
|||||||
func (im *ItemManager) GetWorldDrops(zoneID int32) []*items.Item {
|
func (im *ItemManager) GetWorldDrops(zoneID int32) []*items.Item {
|
||||||
im.mutex.RLock()
|
im.mutex.RLock()
|
||||||
defer im.mutex.RUnlock()
|
defer im.mutex.RUnlock()
|
||||||
|
|
||||||
if drops, exists := im.worldDrops[zoneID]; exists {
|
if drops, exists := im.worldDrops[zoneID]; exists {
|
||||||
// Return a copy to avoid concurrent modification
|
// Return a copy to avoid concurrent modification
|
||||||
result := make([]*items.Item, len(drops))
|
result := make([]*items.Item, len(drops))
|
||||||
copy(result, drops)
|
copy(result, drops)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,16 +175,16 @@ func (im *ItemManager) GetWorldDrops(zoneID int32) []*items.Item {
|
|||||||
func (im *ItemManager) PickupWorldDrop(playerID uint32, itemUniqueID int32, zoneID int32) error {
|
func (im *ItemManager) PickupWorldDrop(playerID uint32, itemUniqueID int32, zoneID int32) error {
|
||||||
im.mutex.Lock()
|
im.mutex.Lock()
|
||||||
defer im.mutex.Unlock()
|
defer im.mutex.Unlock()
|
||||||
|
|
||||||
// Find the item in world drops
|
// Find the item in world drops
|
||||||
drops, exists := im.worldDrops[zoneID]
|
drops, exists := im.worldDrops[zoneID]
|
||||||
if !exists {
|
if !exists {
|
||||||
return fmt.Errorf("no drops in zone %d", zoneID)
|
return fmt.Errorf("no drops in zone %d", zoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var foundItem *items.Item
|
var foundItem *items.Item
|
||||||
var itemIndex int = -1
|
var itemIndex int = -1
|
||||||
|
|
||||||
for i, item := range drops {
|
for i, item := range drops {
|
||||||
if int32(item.Details.UniqueID) == itemUniqueID {
|
if int32(item.Details.UniqueID) == itemUniqueID {
|
||||||
foundItem = item
|
foundItem = item
|
||||||
@ -192,20 +192,20 @@ func (im *ItemManager) PickupWorldDrop(playerID uint32, itemUniqueID int32, zone
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if foundItem == nil {
|
if foundItem == nil {
|
||||||
return fmt.Errorf("item not found: %d", itemUniqueID)
|
return fmt.Errorf("item not found: %d", itemUniqueID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to give item to player
|
// Try to give item to player
|
||||||
err := im.GiveItemToPlayer(playerID, foundItem.Details.ItemID, foundItem.Details.Count)
|
err := im.GiveItemToPlayer(playerID, foundItem.Details.ItemID, foundItem.Details.Count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to give item to player: %w", err)
|
return fmt.Errorf("failed to give item to player: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from world drops
|
// Remove from world drops
|
||||||
im.worldDrops[zoneID] = append(drops[:itemIndex], drops[itemIndex+1:]...)
|
im.worldDrops[zoneID] = append(drops[:itemIndex], drops[itemIndex+1:]...)
|
||||||
|
|
||||||
fmt.Printf("Player %d picked up item: %s x%d\n", playerID, foundItem.Name, foundItem.Details.Count)
|
fmt.Printf("Player %d picked up item: %s x%d\n", playerID, foundItem.Name, foundItem.Details.Count)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -214,12 +214,12 @@ func (im *ItemManager) PickupWorldDrop(playerID uint32, itemUniqueID int32, zone
|
|||||||
func (im *ItemManager) GenerateLootForNPC(npcID int32, killerLevel int16) ([]*items.Item, error) {
|
func (im *ItemManager) GenerateLootForNPC(npcID int32, killerLevel int16) ([]*items.Item, error) {
|
||||||
// TODO: Implement proper loot table lookup and generation
|
// TODO: Implement proper loot table lookup and generation
|
||||||
// For now, create some basic test loot
|
// For now, create some basic test loot
|
||||||
|
|
||||||
testLoot := []*items.Item{}
|
testLoot := []*items.Item{}
|
||||||
|
|
||||||
// Example: Generate some coins based on NPC level
|
// Example: Generate some coins based on NPC level
|
||||||
// This would normally come from loot tables in the database
|
// This would normally come from loot tables in the database
|
||||||
|
|
||||||
fmt.Printf("Generated loot for NPC %d (placeholder implementation)\n", npcID)
|
fmt.Printf("Generated loot for NPC %d (placeholder implementation)\n", npcID)
|
||||||
return testLoot, nil
|
return testLoot, nil
|
||||||
}
|
}
|
||||||
@ -227,18 +227,18 @@ func (im *ItemManager) GenerateLootForNPC(npcID int32, killerLevel int16) ([]*it
|
|||||||
// OnPlayerLogin handles player login - loads their item data
|
// OnPlayerLogin handles player login - loads their item data
|
||||||
func (im *ItemManager) OnPlayerLogin(playerID uint32) error {
|
func (im *ItemManager) OnPlayerLogin(playerID uint32) error {
|
||||||
fmt.Printf("Loading item data for player %d...\n", playerID)
|
fmt.Printf("Loading item data for player %d...\n", playerID)
|
||||||
|
|
||||||
// Pre-load player inventory and equipment
|
// Pre-load player inventory and equipment
|
||||||
_, err := im.GetPlayerInventory(playerID)
|
_, err := im.GetPlayerInventory(playerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load player inventory: %w", err)
|
return fmt.Errorf("failed to load player inventory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = im.GetPlayerEquipment(playerID, items.BaseEquipment)
|
_, err = im.GetPlayerEquipment(playerID, items.BaseEquipment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load player equipment: %w", err)
|
return fmt.Errorf("failed to load player equipment: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Item data loaded for player %d\n", playerID)
|
fmt.Printf("Item data loaded for player %d\n", playerID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -246,16 +246,16 @@ func (im *ItemManager) OnPlayerLogin(playerID uint32) error {
|
|||||||
// OnPlayerLogout handles player logout - saves and clears their item data
|
// OnPlayerLogout handles player logout - saves and clears their item data
|
||||||
func (im *ItemManager) OnPlayerLogout(playerID uint32) error {
|
func (im *ItemManager) OnPlayerLogout(playerID uint32) error {
|
||||||
fmt.Printf("Saving item data for player %d...\n", playerID)
|
fmt.Printf("Saving item data for player %d...\n", playerID)
|
||||||
|
|
||||||
// Save player data
|
// Save player data
|
||||||
err := im.itemSystemAdapter.SavePlayerData(playerID)
|
err := im.itemSystemAdapter.SavePlayerData(playerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save player data: %w", err)
|
return fmt.Errorf("failed to save player data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear cached data
|
// Clear cached data
|
||||||
im.itemSystemAdapter.ClearPlayerData(playerID)
|
im.itemSystemAdapter.ClearPlayerData(playerID)
|
||||||
|
|
||||||
fmt.Printf("Item data saved and cleared for player %d\n", playerID)
|
fmt.Printf("Item data saved and cleared for player %d\n", playerID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -272,26 +272,26 @@ func (im *ItemManager) SearchItems(name string, maxResults int32) []*items.Item
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns item system statistics
|
// GetStatistics returns item system statistics
|
||||||
func (im *ItemManager) GetStatistics() map[string]interface{} {
|
func (im *ItemManager) GetStatistics() map[string]any {
|
||||||
im.mutex.RLock()
|
im.mutex.RLock()
|
||||||
defer im.mutex.RUnlock()
|
defer im.mutex.RUnlock()
|
||||||
|
|
||||||
systemStats := im.itemSystemAdapter.GetSystemStats()
|
systemStats := im.itemSystemAdapter.GetSystemStats()
|
||||||
|
|
||||||
// Add world-specific statistics
|
// Add world-specific statistics
|
||||||
totalWorldDrops := 0
|
totalWorldDrops := 0
|
||||||
for _, drops := range im.worldDrops {
|
for _, drops := range im.worldDrops {
|
||||||
totalWorldDrops += len(drops)
|
totalWorldDrops += len(drops)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
result := make(map[string]any)
|
||||||
for k, v := range systemStats {
|
for k, v := range systemStats {
|
||||||
result[k] = v
|
result[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
result["world_drops"] = totalWorldDrops
|
result["world_drops"] = totalWorldDrops
|
||||||
result["zones_with_drops"] = len(im.worldDrops)
|
result["zones_with_drops"] = len(im.worldDrops)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,15 +303,15 @@ func (im *ItemManager) ValidatePlayerItems(playerID uint32) *items.ItemValidatio
|
|||||||
// Shutdown gracefully shuts down the item manager
|
// Shutdown gracefully shuts down the item manager
|
||||||
func (im *ItemManager) Shutdown() {
|
func (im *ItemManager) Shutdown() {
|
||||||
fmt.Println("Shutting down item manager...")
|
fmt.Println("Shutting down item manager...")
|
||||||
|
|
||||||
im.mutex.Lock()
|
im.mutex.Lock()
|
||||||
defer im.mutex.Unlock()
|
defer im.mutex.Unlock()
|
||||||
|
|
||||||
// Clear all tracking
|
// Clear all tracking
|
||||||
im.playerInventories = make(map[uint32]*items.PlayerItemList)
|
im.playerInventories = make(map[uint32]*items.PlayerItemList)
|
||||||
im.playerEquipment = make(map[uint32]*items.EquipmentItemList)
|
im.playerEquipment = make(map[uint32]*items.EquipmentItemList)
|
||||||
im.worldDrops = make(map[int32][]*items.Item)
|
im.worldDrops = make(map[int32][]*items.Item)
|
||||||
|
|
||||||
fmt.Println("Item manager shutdown complete")
|
fmt.Println("Item manager shutdown complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +323,7 @@ type WorldItemDatabaseAdapter struct {
|
|||||||
// LoadItems implements items.DatabaseService interface
|
// LoadItems implements items.DatabaseService interface
|
||||||
func (wdb *WorldItemDatabaseAdapter) LoadItems(masterList *items.MasterItemList) error {
|
func (wdb *WorldItemDatabaseAdapter) LoadItems(masterList *items.MasterItemList) error {
|
||||||
fmt.Println("Loading items from database...")
|
fmt.Println("Loading items from database...")
|
||||||
|
|
||||||
// Load item templates from database
|
// Load item templates from database
|
||||||
rows, err := wdb.db.Query(`
|
rows, err := wdb.db.Query(`
|
||||||
SELECT id, name, item_type, icon, description, tier, level, classes, races,
|
SELECT id, name, item_type, icon, description, tier, level, classes, races,
|
||||||
@ -336,17 +336,17 @@ func (wdb *WorldItemDatabaseAdapter) LoadItems(masterList *items.MasterItemList)
|
|||||||
return fmt.Errorf("failed to query items: %w", err)
|
return fmt.Errorf("failed to query items: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
itemCount := 0
|
itemCount := 0
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
// Create new item from database data
|
// Create new item from database data
|
||||||
item := items.NewItem()
|
item := items.NewItem()
|
||||||
|
|
||||||
var id, itemType, icon, tier, level, stackCount int32
|
var id, itemType, icon, tier, level, stackCount int32
|
||||||
var weight, genericInfo, bagInfo, foodInfo, weaponInfo, rangedInfo int32
|
var weight, genericInfo, bagInfo, foodInfo, weaponInfo, rangedInfo int32
|
||||||
var details, appearance int32
|
var details, appearance int32
|
||||||
var name, description, classes, races, adventureClasses, tradeskillClasses string
|
var name, description, classes, races, adventureClasses, tradeskillClasses string
|
||||||
|
|
||||||
err := rows.Scan(&id, &name, &itemType, &icon, &description, &tier, &level,
|
err := rows.Scan(&id, &name, &itemType, &icon, &description, &tier, &level,
|
||||||
&classes, &races, &adventureClasses, &tradeskillClasses, &stackCount,
|
&classes, &races, &adventureClasses, &tradeskillClasses, &stackCount,
|
||||||
&weight, &genericInfo, &bagInfo, &foodInfo, &weaponInfo, &rangedInfo,
|
&weight, &genericInfo, &bagInfo, &foodInfo, &weaponInfo, &rangedInfo,
|
||||||
@ -355,7 +355,7 @@ func (wdb *WorldItemDatabaseAdapter) LoadItems(masterList *items.MasterItemList)
|
|||||||
fmt.Printf("Error scanning item row: %v\n", err)
|
fmt.Printf("Error scanning item row: %v\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set item properties
|
// Set item properties
|
||||||
item.Details.ItemID = id
|
item.Details.ItemID = id
|
||||||
item.Name = name
|
item.Name = name
|
||||||
@ -366,25 +366,25 @@ func (wdb *WorldItemDatabaseAdapter) LoadItems(masterList *items.MasterItemList)
|
|||||||
item.StackCount = int16(stackCount)
|
item.StackCount = int16(stackCount)
|
||||||
// TODO: Set weight when field is available
|
// TODO: Set weight when field is available
|
||||||
// item.Weight = weight
|
// item.Weight = weight
|
||||||
|
|
||||||
// TODO: Parse class/race strings and set appropriate fields
|
// TODO: Parse class/race strings and set appropriate fields
|
||||||
// item.SetClasses(parseClassString(classes))
|
// item.SetClasses(parseClassString(classes))
|
||||||
// item.SetRaces(parseRaceString(races))
|
// item.SetRaces(parseRaceString(races))
|
||||||
|
|
||||||
// Add to master list
|
// Add to master list
|
||||||
masterList.AddItem(item)
|
masterList.AddItem(item)
|
||||||
itemCount++
|
itemCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return fmt.Errorf("error iterating item rows: %w", err)
|
return fmt.Errorf("error iterating item rows: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Successfully loaded %d item templates from database\n", itemCount)
|
fmt.Printf("Successfully loaded %d item templates from database\n", itemCount)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveItem implements items.DatabaseService interface
|
// SaveItem implements items.DatabaseService interface
|
||||||
func (wdb *WorldItemDatabaseAdapter) SaveItem(item *items.Item) error {
|
func (wdb *WorldItemDatabaseAdapter) SaveItem(item *items.Item) error {
|
||||||
// TODO: Implement item template saving
|
// TODO: Implement item template saving
|
||||||
fmt.Printf("Saving item template: %s (ID: %d) - not yet implemented\n", item.Name, item.Details.ItemID)
|
fmt.Printf("Saving item template: %s (ID: %d) - not yet implemented\n", item.Name, item.Details.ItemID)
|
||||||
@ -432,27 +432,27 @@ func (wdb *WorldItemDatabaseAdapter) SavePlayerEquipment(playerID uint32, equipm
|
|||||||
func (wdb *WorldItemDatabaseAdapter) LoadItemStats() (map[string]int32, map[int32]string, error) {
|
func (wdb *WorldItemDatabaseAdapter) LoadItemStats() (map[string]int32, map[int32]string, error) {
|
||||||
// TODO: Implement item stat mapping loading
|
// TODO: Implement item stat mapping loading
|
||||||
fmt.Println("Loading item stats - using placeholder data")
|
fmt.Println("Loading item stats - using placeholder data")
|
||||||
|
|
||||||
statsStrings := map[string]int32{
|
statsStrings := map[string]int32{
|
||||||
"health": 1,
|
"health": 1,
|
||||||
"power": 2,
|
"power": 2,
|
||||||
"strength": 3,
|
"strength": 3,
|
||||||
"stamina": 4,
|
"stamina": 4,
|
||||||
"agility": 5,
|
"agility": 5,
|
||||||
"wisdom": 6,
|
"wisdom": 6,
|
||||||
"intelligence": 7,
|
"intelligence": 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
statsIDs := map[int32]string{
|
statsIDs := map[int32]string{
|
||||||
1: "health",
|
1: "health",
|
||||||
2: "power",
|
2: "power",
|
||||||
3: "strength",
|
3: "strength",
|
||||||
4: "stamina",
|
4: "stamina",
|
||||||
5: "agility",
|
5: "agility",
|
||||||
6: "wisdom",
|
6: "wisdom",
|
||||||
7: "intelligence",
|
7: "intelligence",
|
||||||
}
|
}
|
||||||
|
|
||||||
return statsStrings, statsIDs, nil
|
return statsStrings, statsIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,14 +601,14 @@ type MockPlayer struct {
|
|||||||
alignment int8
|
alignment int8
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *MockPlayer) GetID() uint32 { return mp.id }
|
func (mp *MockPlayer) GetID() uint32 { return mp.id }
|
||||||
func (mp *MockPlayer) GetName() string { return mp.name }
|
func (mp *MockPlayer) GetName() string { return mp.name }
|
||||||
func (mp *MockPlayer) GetLevel() int16 { return mp.level }
|
func (mp *MockPlayer) GetLevel() int16 { return mp.level }
|
||||||
func (mp *MockPlayer) GetAdventureClass() int8 { return mp.adventureClass }
|
func (mp *MockPlayer) GetAdventureClass() int8 { return mp.adventureClass }
|
||||||
func (mp *MockPlayer) GetTradeskillClass() int8 { return mp.tradeskillClass }
|
func (mp *MockPlayer) GetTradeskillClass() int8 { return mp.tradeskillClass }
|
||||||
func (mp *MockPlayer) GetRace() int8 { return mp.race }
|
func (mp *MockPlayer) GetRace() int8 { return mp.race }
|
||||||
func (mp *MockPlayer) GetGender() int8 { return mp.gender }
|
func (mp *MockPlayer) GetGender() int8 { return mp.gender }
|
||||||
func (mp *MockPlayer) GetAlignment() int8 { return mp.alignment }
|
func (mp *MockPlayer) GetAlignment() int8 { return mp.alignment }
|
||||||
|
|
||||||
// MockItemQuestAdapter provides mock quest functionality
|
// MockItemQuestAdapter provides mock quest functionality
|
||||||
type MockItemQuestAdapter struct{}
|
type MockItemQuestAdapter struct{}
|
||||||
@ -676,4 +676,4 @@ func (mha *MockItemHousingAdapter) RemoveItem(playerID uint32, houseID int32, it
|
|||||||
|
|
||||||
func (mha *MockItemHousingAdapter) GetHouseItems(houseID int32) ([]*items.Item, error) {
|
func (mha *MockItemHousingAdapter) GetHouseItems(houseID int32) ([]*items.Item, error) {
|
||||||
return []*items.Item{}, nil
|
return []*items.Item{}, nil
|
||||||
}
|
}
|
||||||
|
@ -3,24 +3,24 @@ package world
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"eq2emu/internal/database"
|
||||||
"eq2emu/internal/npc"
|
"eq2emu/internal/npc"
|
||||||
"eq2emu/internal/npc/ai"
|
"eq2emu/internal/npc/ai"
|
||||||
"eq2emu/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NPCManager manages NPCs for the world server
|
// NPCManager manages NPCs for the world server
|
||||||
type NPCManager struct {
|
type NPCManager struct {
|
||||||
npcManager *npc.Manager
|
npcManager *npc.Manager
|
||||||
aiManager *ai.AIManager
|
aiManager *ai.AIManager
|
||||||
database *database.Database
|
database *database.Database
|
||||||
world *World // Reference to world server
|
world *World // Reference to world server
|
||||||
|
|
||||||
// World-specific NPC tracking
|
// World-specific NPC tracking
|
||||||
npcsByZone map[int32][]*npc.NPC // Zone ID -> NPCs
|
npcsByZone map[int32][]*npc.NPC // Zone ID -> NPCs
|
||||||
activeCombat map[int32]bool // NPC ID -> in combat
|
activeCombat map[int32]bool // NPC ID -> in combat
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNPCManager creates a new NPC manager for the world server
|
// NewNPCManager creates a new NPC manager for the world server
|
||||||
@ -28,13 +28,13 @@ func NewNPCManager(db *database.Database) *NPCManager {
|
|||||||
// Create adapters for the NPC system
|
// Create adapters for the NPC system
|
||||||
dbAdapter := &WorldNPCDatabaseAdapter{db: db}
|
dbAdapter := &WorldNPCDatabaseAdapter{db: db}
|
||||||
logAdapter := &WorldNPCLoggerAdapter{}
|
logAdapter := &WorldNPCLoggerAdapter{}
|
||||||
|
|
||||||
// Create core NPC manager
|
// Create core NPC manager
|
||||||
npcMgr := npc.NewManager(dbAdapter, logAdapter)
|
npcMgr := npc.NewManager(dbAdapter, logAdapter)
|
||||||
|
|
||||||
// Create AI manager with logger
|
// Create AI manager with logger
|
||||||
aiMgr := ai.NewAIManager(logAdapter, nil) // No Lua interface for now
|
aiMgr := ai.NewAIManager(logAdapter, nil) // No Lua interface for now
|
||||||
|
|
||||||
return &NPCManager{
|
return &NPCManager{
|
||||||
npcManager: npcMgr,
|
npcManager: npcMgr,
|
||||||
aiManager: aiMgr,
|
aiManager: aiMgr,
|
||||||
@ -52,13 +52,13 @@ func (nm *NPCManager) SetWorld(world *World) {
|
|||||||
// LoadNPCs loads all NPCs from database
|
// LoadNPCs loads all NPCs from database
|
||||||
func (nm *NPCManager) LoadNPCs() error {
|
func (nm *NPCManager) LoadNPCs() error {
|
||||||
fmt.Println("Loading NPC data...")
|
fmt.Println("Loading NPC data...")
|
||||||
|
|
||||||
// Initialize the NPC manager
|
// Initialize the NPC manager
|
||||||
err := nm.npcManager.Initialize()
|
err := nm.npcManager.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize NPC manager: %w", err)
|
return fmt.Errorf("failed to initialize NPC manager: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load NPCs from database
|
// Load NPCs from database
|
||||||
dbAdapter := &WorldNPCDatabaseAdapter{db: nm.database}
|
dbAdapter := &WorldNPCDatabaseAdapter{db: nm.database}
|
||||||
dbNPCs, err := dbAdapter.LoadAllNPCs()
|
dbNPCs, err := dbAdapter.LoadAllNPCs()
|
||||||
@ -72,7 +72,7 @@ func (nm *NPCManager) LoadNPCs() error {
|
|||||||
// Add to zone tracking (using zone ID from database or default)
|
// Add to zone tracking (using zone ID from database or default)
|
||||||
zoneID := int32(1) // TODO: Get actual zone ID from NPC
|
zoneID := int32(1) // TODO: Get actual zone ID from NPC
|
||||||
nm.AddNPCToZone(zoneID, npc)
|
nm.AddNPCToZone(zoneID, npc)
|
||||||
|
|
||||||
// Create AI brain for this NPC (skip for now due to interface incompatibility)
|
// Create AI brain for this NPC (skip for now due to interface incompatibility)
|
||||||
// TODO: Create proper NPC-to-AI adapter or implement missing methods
|
// TODO: Create proper NPC-to-AI adapter or implement missing methods
|
||||||
// err := nm.aiManager.CreateBrainForNPC(npc, ai.BrainTypeDefault)
|
// err := nm.aiManager.CreateBrainForNPC(npc, ai.BrainTypeDefault)
|
||||||
@ -83,13 +83,13 @@ func (nm *NPCManager) LoadNPCs() error {
|
|||||||
}
|
}
|
||||||
fmt.Printf("Loaded %d NPCs from database\n", len(dbNPCs))
|
fmt.Printf("Loaded %d NPCs from database\n", len(dbNPCs))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup default/test NPCs for development
|
// Setup default/test NPCs for development
|
||||||
err = nm.createTestNPCs()
|
err = nm.createTestNPCs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Warning: Failed to create test NPCs: %v\n", err)
|
fmt.Printf("Warning: Failed to create test NPCs: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := nm.npcManager.GetStatistics()
|
stats := nm.npcManager.GetStatistics()
|
||||||
fmt.Printf("Total NPCs loaded: %d\n", stats.TotalNPCs)
|
fmt.Printf("Total NPCs loaded: %d\n", stats.TotalNPCs)
|
||||||
return nil
|
return nil
|
||||||
@ -98,7 +98,7 @@ func (nm *NPCManager) LoadNPCs() error {
|
|||||||
// createTestNPCs creates some test NPCs for development
|
// createTestNPCs creates some test NPCs for development
|
||||||
func (nm *NPCManager) createTestNPCs() error {
|
func (nm *NPCManager) createTestNPCs() error {
|
||||||
fmt.Println("Creating test NPCs for development...")
|
fmt.Println("Creating test NPCs for development...")
|
||||||
|
|
||||||
// Create a few test NPCs using npc.NewNPC()
|
// Create a few test NPCs using npc.NewNPC()
|
||||||
testNPCs := []struct {
|
testNPCs := []struct {
|
||||||
id int32
|
id int32
|
||||||
@ -111,47 +111,47 @@ func (nm *NPCManager) createTestNPCs() error {
|
|||||||
{1003, "Test Forest Bear", 15, 2},
|
{1003, "Test Forest Bear", 15, 2},
|
||||||
{1004, "Test Fire Elemental", 20, 3},
|
{1004, "Test Fire Elemental", 20, 3},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testData := range testNPCs {
|
for _, testData := range testNPCs {
|
||||||
// Create new NPC instance
|
// Create new NPC instance
|
||||||
newNPC := npc.NewNPC()
|
newNPC := npc.NewNPC()
|
||||||
if newNPC == nil {
|
if newNPC == nil {
|
||||||
return fmt.Errorf("failed to create NPC %s", testData.name)
|
return fmt.Errorf("failed to create NPC %s", testData.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set basic NPC properties
|
// Set basic NPC properties
|
||||||
newNPC.SetID(testData.id)
|
newNPC.SetID(testData.id)
|
||||||
newNPC.SetName(testData.name)
|
newNPC.SetName(testData.name)
|
||||||
newNPC.SetLevel(int16(testData.level))
|
newNPC.SetLevel(int16(testData.level))
|
||||||
|
|
||||||
// Set some default stats based on level
|
// Set some default stats based on level
|
||||||
baseHP := int32(testData.level) * 50
|
baseHP := int32(testData.level) * 50
|
||||||
newNPC.SetTotalHP(baseHP)
|
newNPC.SetTotalHP(baseHP)
|
||||||
newNPC.SetHP(baseHP)
|
newNPC.SetHP(baseHP)
|
||||||
|
|
||||||
// Set position (placeholder coordinates)
|
// Set position (placeholder coordinates)
|
||||||
x := float32(100 + testData.id)
|
x := float32(100 + testData.id)
|
||||||
y := float32(100 + testData.id)
|
y := float32(100 + testData.id)
|
||||||
z := float32(50)
|
z := float32(50)
|
||||||
newNPC.SetX(x)
|
newNPC.SetX(x)
|
||||||
newNPC.SetY(y, false) // SetY takes bool parameter
|
newNPC.SetY(y, false) // SetY takes bool parameter
|
||||||
newNPC.SetZ(z)
|
newNPC.SetZ(z)
|
||||||
newNPC.SetHeadingFromFloat(0.0)
|
newNPC.SetHeadingFromFloat(0.0)
|
||||||
|
|
||||||
// Add to zone tracking
|
// Add to zone tracking
|
||||||
nm.AddNPCToZone(testData.zoneID, newNPC)
|
nm.AddNPCToZone(testData.zoneID, newNPC)
|
||||||
|
|
||||||
// Create an AI brain for this NPC (skip for now due to interface incompatibility)
|
// Create an AI brain for this NPC (skip for now due to interface incompatibility)
|
||||||
// TODO: Create proper NPC-to-AI adapter or implement missing methods
|
// TODO: Create proper NPC-to-AI adapter or implement missing methods
|
||||||
// err := nm.aiManager.CreateBrainForNPC(newNPC, ai.BrainTypeDefault)
|
// err := nm.aiManager.CreateBrainForNPC(newNPC, ai.BrainTypeDefault)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// fmt.Printf("Warning: Failed to create AI brain for NPC %s: %v\n", testData.name, err)
|
// fmt.Printf("Warning: Failed to create AI brain for NPC %s: %v\n", testData.name, err)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fmt.Printf("Created test NPC: %s (ID: %d, Level: %d, Zone: %d)\n",
|
fmt.Printf("Created test NPC: %s (ID: %d, Level: %d, Zone: %d)\n",
|
||||||
testData.name, testData.id, testData.level, testData.zoneID)
|
testData.name, testData.id, testData.level, testData.zoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Successfully created %d test NPCs\n", len(testNPCs))
|
fmt.Printf("Successfully created %d test NPCs\n", len(testNPCs))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -160,10 +160,10 @@ func (nm *NPCManager) createTestNPCs() error {
|
|||||||
func (nm *NPCManager) ProcessNPCs() {
|
func (nm *NPCManager) ProcessNPCs() {
|
||||||
nm.mutex.RLock()
|
nm.mutex.RLock()
|
||||||
defer nm.mutex.RUnlock()
|
defer nm.mutex.RUnlock()
|
||||||
|
|
||||||
// Process AI for all NPCs
|
// Process AI for all NPCs
|
||||||
nm.aiManager.ProcessAllBrains()
|
nm.aiManager.ProcessAllBrains()
|
||||||
|
|
||||||
// Process NPC-specific logic
|
// Process NPC-specific logic
|
||||||
stats := nm.npcManager.GetStatistics()
|
stats := nm.npcManager.GetStatistics()
|
||||||
if stats.NPCsInCombat > 0 {
|
if stats.NPCsInCombat > 0 {
|
||||||
@ -176,13 +176,13 @@ func (nm *NPCManager) ProcessNPCs() {
|
|||||||
func (nm *NPCManager) processCombatNPCs() {
|
func (nm *NPCManager) processCombatNPCs() {
|
||||||
nm.mutex.RLock()
|
nm.mutex.RLock()
|
||||||
defer nm.mutex.RUnlock()
|
defer nm.mutex.RUnlock()
|
||||||
|
|
||||||
// Process each NPC that's in combat
|
// Process each NPC that's in combat
|
||||||
for npcID, inCombat := range nm.activeCombat {
|
for npcID, inCombat := range nm.activeCombat {
|
||||||
if !inCombat {
|
if !inCombat {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the NPC in our zone tracking
|
// Find the NPC in our zone tracking
|
||||||
var combatNPC *npc.NPC
|
var combatNPC *npc.NPC
|
||||||
for _, npcs := range nm.npcsByZone {
|
for _, npcs := range nm.npcsByZone {
|
||||||
@ -196,19 +196,19 @@ func (nm *NPCManager) processCombatNPCs() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if combatNPC == nil {
|
if combatNPC == nil {
|
||||||
// NPC not found, remove from combat tracking
|
// NPC not found, remove from combat tracking
|
||||||
delete(nm.activeCombat, npcID)
|
delete(nm.activeCombat, npcID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if NPC is dead or incapacitated
|
// Skip if NPC is dead or incapacitated
|
||||||
if !combatNPC.IsAlive() {
|
if !combatNPC.IsAlive() {
|
||||||
delete(nm.activeCombat, npcID)
|
delete(nm.activeCombat, npcID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process combat AI
|
// Process combat AI
|
||||||
nm.processCombatNPC(combatNPC)
|
nm.processCombatNPC(combatNPC)
|
||||||
}
|
}
|
||||||
@ -219,21 +219,21 @@ func (nm *NPCManager) processCombatNPC(npc *npc.NPC) {
|
|||||||
if npc == nil {
|
if npc == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
npcID := npc.GetID()
|
npcID := npc.GetID()
|
||||||
// TODO: Implement proper target system - GetTarget() returns int32 not Entity
|
// TODO: Implement proper target system - GetTarget() returns int32 not Entity
|
||||||
// For now, simulate combat without target validation
|
// For now, simulate combat without target validation
|
||||||
|
|
||||||
// Check if NPC should still be in combat (placeholder logic)
|
// Check if NPC should still be in combat (placeholder logic)
|
||||||
if !npc.IsAlive() {
|
if !npc.IsAlive() {
|
||||||
npc.InCombat(false)
|
npc.InCombat(false)
|
||||||
delete(nm.activeCombat, npcID)
|
delete(nm.activeCombat, npcID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplified combat processing (placeholder until target system is implemented)
|
// Simplified combat processing (placeholder until target system is implemented)
|
||||||
fmt.Printf("NPC %s (%d) processing combat AI (simplified)\n", npc.GetName(), npcID)
|
fmt.Printf("NPC %s (%d) processing combat AI (simplified)\n", npc.GetName(), npcID)
|
||||||
|
|
||||||
// For now, just simulate combat for a few seconds then exit combat
|
// For now, just simulate combat for a few seconds then exit combat
|
||||||
// TODO: Implement proper combat mechanics with target system
|
// TODO: Implement proper combat mechanics with target system
|
||||||
// This is a placeholder to test the basic NPC system
|
// This is a placeholder to test the basic NPC system
|
||||||
@ -247,7 +247,7 @@ func (nm *NPCManager) processCombatNPC(npc *npc.NPC) {
|
|||||||
// // Implementation deferred until interface issues are resolved
|
// // Implementation deferred until interface issues are resolved
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// processCombatMelee handles NPC melee combat
|
// processCombatMelee handles NPC melee combat
|
||||||
// func (nm *NPCManager) processCombatMelee(npc *npc.NPC, target entity.Entity, distance float32) {
|
// func (nm *NPCManager) processCombatMelee(npc *npc.NPC, target entity.Entity, distance float32) {
|
||||||
// // Implementation deferred until interface issues are resolved
|
// // Implementation deferred until interface issues are resolved
|
||||||
// }
|
// }
|
||||||
@ -262,7 +262,7 @@ func (nm *NPCManager) OnNPCKilled(npcID int32, killerCharacterID int32) {
|
|||||||
nm.mutex.Lock()
|
nm.mutex.Lock()
|
||||||
delete(nm.activeCombat, npcID)
|
delete(nm.activeCombat, npcID)
|
||||||
nm.mutex.Unlock()
|
nm.mutex.Unlock()
|
||||||
|
|
||||||
// Trigger achievement events
|
// Trigger achievement events
|
||||||
if nm.world != nil && nm.world.achievementMgr != nil {
|
if nm.world != nil && nm.world.achievementMgr != nil {
|
||||||
// Get NPC info
|
// Get NPC info
|
||||||
@ -272,7 +272,7 @@ func (nm *NPCManager) OnNPCKilled(npcID int32, killerCharacterID int32) {
|
|||||||
nm.world.OnNPCKill(killerCharacterID, npcID, int32(npcInfo.Level))
|
nm.world.OnNPCKill(killerCharacterID, npcID, int32(npcInfo.Level))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate and distribute loot
|
// Generate and distribute loot
|
||||||
if nm.world != nil && nm.world.itemMgr != nil {
|
if nm.world != nil && nm.world.itemMgr != nil {
|
||||||
npcInfo := nm.GetNPCInfo(npcID)
|
npcInfo := nm.GetNPCInfo(npcID)
|
||||||
@ -289,19 +289,19 @@ func (nm *NPCManager) OnNPCKilled(npcID int32, killerCharacterID int32) {
|
|||||||
if zoneID == 0 {
|
if zoneID == 0 {
|
||||||
zoneID = 1 // Default zone
|
zoneID = 1 // Default zone
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range loot {
|
for _, item := range loot {
|
||||||
err := nm.world.itemMgr.CreateWorldDrop(item.Details.ItemID, item.Details.Count, x, y, z, zoneID)
|
err := nm.world.itemMgr.CreateWorldDrop(item.Details.ItemID, item.Details.Count, x, y, z, zoneID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to create loot drop for item %d: %v\n", item.Details.ItemID, err)
|
fmt.Printf("Failed to create loot drop for item %d: %v\n", item.Details.ItemID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Generated %d loot items for NPC %d kill\n", len(loot), npcID)
|
fmt.Printf("Generated %d loot items for NPC %d kill\n", len(loot), npcID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("NPC %d killed by character %d\n", npcID, killerCharacterID)
|
fmt.Printf("NPC %d killed by character %d\n", npcID, killerCharacterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +310,7 @@ func (nm *NPCManager) OnNPCEnteredCombat(npcID int32, targetID int32) {
|
|||||||
nm.mutex.Lock()
|
nm.mutex.Lock()
|
||||||
nm.activeCombat[npcID] = true
|
nm.activeCombat[npcID] = true
|
||||||
nm.mutex.Unlock()
|
nm.mutex.Unlock()
|
||||||
|
|
||||||
fmt.Printf("NPC %d entered combat with target %d\n", npcID, targetID)
|
fmt.Printf("NPC %d entered combat with target %d\n", npcID, targetID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,7 +319,7 @@ func (nm *NPCManager) OnNPCLeftCombat(npcID int32) {
|
|||||||
nm.mutex.Lock()
|
nm.mutex.Lock()
|
||||||
delete(nm.activeCombat, npcID)
|
delete(nm.activeCombat, npcID)
|
||||||
nm.mutex.Unlock()
|
nm.mutex.Unlock()
|
||||||
|
|
||||||
fmt.Printf("NPC %d left combat\n", npcID)
|
fmt.Printf("NPC %d left combat\n", npcID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ func (nm *NPCManager) OnNPCLeftCombat(npcID int32) {
|
|||||||
func (nm *NPCManager) GetNPCInfo(npcID int32) *NPCInfo {
|
func (nm *NPCManager) GetNPCInfo(npcID int32) *NPCInfo {
|
||||||
nm.mutex.RLock()
|
nm.mutex.RLock()
|
||||||
defer nm.mutex.RUnlock()
|
defer nm.mutex.RUnlock()
|
||||||
|
|
||||||
// First, check if we have the NPC in memory
|
// First, check if we have the NPC in memory
|
||||||
for _, npcs := range nm.npcsByZone {
|
for _, npcs := range nm.npcsByZone {
|
||||||
for _, npc := range npcs {
|
for _, npc := range npcs {
|
||||||
@ -341,18 +341,18 @@ func (nm *NPCManager) GetNPCInfo(npcID int32) *NPCInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not in memory, try to load from database
|
// If not in memory, try to load from database
|
||||||
row := nm.database.QueryRow(`
|
row := nm.database.QueryRow(`
|
||||||
SELECT id, name, level, zone_id
|
SELECT id, name, level, zone_id
|
||||||
FROM npcs
|
FROM npcs
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`, npcID)
|
`, npcID)
|
||||||
|
|
||||||
var id, zoneID int32
|
var id, zoneID int32
|
||||||
var level int8
|
var level int8
|
||||||
var name string
|
var name string
|
||||||
|
|
||||||
err := row.Scan(&id, &name, &level, &zoneID)
|
err := row.Scan(&id, &name, &level, &zoneID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// NPC not found in database either, return default info
|
// NPC not found in database either, return default info
|
||||||
@ -363,7 +363,7 @@ func (nm *NPCManager) GetNPCInfo(npcID int32) *NPCInfo {
|
|||||||
ZoneID: 0,
|
ZoneID: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &NPCInfo{
|
return &NPCInfo{
|
||||||
ID: id,
|
ID: id,
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -376,14 +376,14 @@ func (nm *NPCManager) GetNPCInfo(npcID int32) *NPCInfo {
|
|||||||
func (nm *NPCManager) GetNPCsByZone(zoneID int32) []*npc.NPC {
|
func (nm *NPCManager) GetNPCsByZone(zoneID int32) []*npc.NPC {
|
||||||
nm.mutex.RLock()
|
nm.mutex.RLock()
|
||||||
defer nm.mutex.RUnlock()
|
defer nm.mutex.RUnlock()
|
||||||
|
|
||||||
if npcs, exists := nm.npcsByZone[zoneID]; exists {
|
if npcs, exists := nm.npcsByZone[zoneID]; exists {
|
||||||
// Return a copy to avoid concurrent modification
|
// Return a copy to avoid concurrent modification
|
||||||
result := make([]*npc.NPC, len(npcs))
|
result := make([]*npc.NPC, len(npcs))
|
||||||
copy(result, npcs)
|
copy(result, npcs)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,7 +391,7 @@ func (nm *NPCManager) GetNPCsByZone(zoneID int32) []*npc.NPC {
|
|||||||
func (nm *NPCManager) AddNPCToZone(zoneID int32, npc *npc.NPC) {
|
func (nm *NPCManager) AddNPCToZone(zoneID int32, npc *npc.NPC) {
|
||||||
nm.mutex.Lock()
|
nm.mutex.Lock()
|
||||||
defer nm.mutex.Unlock()
|
defer nm.mutex.Unlock()
|
||||||
|
|
||||||
nm.npcsByZone[zoneID] = append(nm.npcsByZone[zoneID], npc)
|
nm.npcsByZone[zoneID] = append(nm.npcsByZone[zoneID], npc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +399,7 @@ func (nm *NPCManager) AddNPCToZone(zoneID int32, npc *npc.NPC) {
|
|||||||
func (nm *NPCManager) RemoveNPCFromZone(zoneID int32, npcID int32) {
|
func (nm *NPCManager) RemoveNPCFromZone(zoneID int32, npcID int32) {
|
||||||
nm.mutex.Lock()
|
nm.mutex.Lock()
|
||||||
defer nm.mutex.Unlock()
|
defer nm.mutex.Unlock()
|
||||||
|
|
||||||
if npcs, exists := nm.npcsByZone[zoneID]; exists {
|
if npcs, exists := nm.npcsByZone[zoneID]; exists {
|
||||||
for i, npc := range npcs {
|
for i, npc := range npcs {
|
||||||
if npc != nil && npc.GetID() == npcID {
|
if npc != nil && npc.GetID() == npcID {
|
||||||
@ -412,56 +412,56 @@ func (nm *NPCManager) RemoveNPCFromZone(zoneID int32, npcID int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns NPC system statistics
|
// GetStatistics returns NPC system statistics
|
||||||
func (nm *NPCManager) GetStatistics() map[string]interface{} {
|
func (nm *NPCManager) GetStatistics() map[string]any {
|
||||||
nm.mutex.RLock()
|
nm.mutex.RLock()
|
||||||
defer nm.mutex.RUnlock()
|
defer nm.mutex.RUnlock()
|
||||||
|
|
||||||
// Get base statistics from NPC manager
|
// Get base statistics from NPC manager
|
||||||
stats := nm.npcManager.GetStatistics()
|
stats := nm.npcManager.GetStatistics()
|
||||||
|
|
||||||
// Add world-specific statistics
|
// Add world-specific statistics
|
||||||
totalZones := len(nm.npcsByZone)
|
totalZones := len(nm.npcsByZone)
|
||||||
totalInCombat := len(nm.activeCombat)
|
totalInCombat := len(nm.activeCombat)
|
||||||
|
|
||||||
result := map[string]interface{}{
|
result := map[string]any{
|
||||||
"total_npcs": stats.TotalNPCs,
|
"total_npcs": stats.TotalNPCs,
|
||||||
"npcs_in_combat": stats.NPCsInCombat,
|
"npcs_in_combat": stats.NPCsInCombat,
|
||||||
"npcs_with_spells": stats.NPCsWithSpells,
|
"npcs_with_spells": stats.NPCsWithSpells,
|
||||||
"npcs_with_skills": stats.NPCsWithSkills,
|
"npcs_with_skills": stats.NPCsWithSkills,
|
||||||
"spell_cast_count": stats.SpellCastCount,
|
"spell_cast_count": stats.SpellCastCount,
|
||||||
"skill_usage_count": stats.SkillUsageCount,
|
"skill_usage_count": stats.SkillUsageCount,
|
||||||
"runback_count": stats.RunbackCount,
|
"runback_count": stats.RunbackCount,
|
||||||
"average_aggro_radius": stats.AverageAggroRadius,
|
"average_aggro_radius": stats.AverageAggroRadius,
|
||||||
"ai_strategy_counts": stats.AIStrategyCounts,
|
"ai_strategy_counts": stats.AIStrategyCounts,
|
||||||
"zones_with_npcs": totalZones,
|
"zones_with_npcs": totalZones,
|
||||||
"world_npcs_in_combat": totalInCombat,
|
"world_npcs_in_combat": totalInCombat,
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown gracefully shuts down the NPC manager
|
// Shutdown gracefully shuts down the NPC manager
|
||||||
func (nm *NPCManager) Shutdown() {
|
func (nm *NPCManager) Shutdown() {
|
||||||
fmt.Println("Shutting down NPC manager...")
|
fmt.Println("Shutting down NPC manager...")
|
||||||
|
|
||||||
nm.mutex.Lock()
|
nm.mutex.Lock()
|
||||||
defer nm.mutex.Unlock()
|
defer nm.mutex.Unlock()
|
||||||
|
|
||||||
// Clear all tracking
|
// Clear all tracking
|
||||||
nm.npcsByZone = make(map[int32][]*npc.NPC)
|
nm.npcsByZone = make(map[int32][]*npc.NPC)
|
||||||
nm.activeCombat = make(map[int32]bool)
|
nm.activeCombat = make(map[int32]bool)
|
||||||
|
|
||||||
// TODO: Shutdown AI manager when shutdown method is available
|
// TODO: Shutdown AI manager when shutdown method is available
|
||||||
// nm.aiManager.Shutdown()
|
// nm.aiManager.Shutdown()
|
||||||
|
|
||||||
fmt.Println("NPC manager shutdown complete")
|
fmt.Println("NPC manager shutdown complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NPCInfo represents basic information about an NPC
|
// NPCInfo represents basic information about an NPC
|
||||||
type NPCInfo struct {
|
type NPCInfo struct {
|
||||||
ID int32
|
ID int32
|
||||||
Name string
|
Name string
|
||||||
Level int8
|
Level int8
|
||||||
ZoneID int32
|
ZoneID int32
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,7 +473,7 @@ type WorldNPCDatabaseAdapter struct {
|
|||||||
// LoadAllNPCs implements npc.Database interface
|
// LoadAllNPCs implements npc.Database interface
|
||||||
func (wdb *WorldNPCDatabaseAdapter) LoadAllNPCs() ([]*npc.NPC, error) {
|
func (wdb *WorldNPCDatabaseAdapter) LoadAllNPCs() ([]*npc.NPC, error) {
|
||||||
fmt.Println("Loading NPCs from database...")
|
fmt.Println("Loading NPCs from database...")
|
||||||
|
|
||||||
rows, err := wdb.db.Query(`
|
rows, err := wdb.db.Query(`
|
||||||
SELECT id, name, level, max_level, race, model_type, size, hp, power,
|
SELECT id, name, level, max_level, race, model_type, size, hp, power,
|
||||||
x, y, z, heading, respawn_time, zone_id, aggro_radius, ai_strategy,
|
x, y, z, heading, respawn_time, zone_id, aggro_radius, ai_strategy,
|
||||||
@ -486,22 +486,22 @@ func (wdb *WorldNPCDatabaseAdapter) LoadAllNPCs() ([]*npc.NPC, error) {
|
|||||||
return nil, fmt.Errorf("failed to query NPCs: %w", err)
|
return nil, fmt.Errorf("failed to query NPCs: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var npcs []*npc.NPC
|
var npcs []*npc.NPC
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
newNPC := npc.NewNPC()
|
newNPC := npc.NewNPC()
|
||||||
if newNPC == nil {
|
if newNPC == nil {
|
||||||
continue // Skip if we can't create NPC
|
continue // Skip if we can't create NPC
|
||||||
}
|
}
|
||||||
|
|
||||||
var id, maxLevel, race, modelType, size, hp, power int32
|
var id, maxLevel, race, modelType, size, hp, power int32
|
||||||
var x, y, z, heading, aggroRadius float32
|
var x, y, z, heading, aggroRadius float32
|
||||||
var respawnTime, zoneID, aiStrategy, lootTableID, merchantType int32
|
var respawnTime, zoneID, aiStrategy, lootTableID, merchantType int32
|
||||||
var randomizeAppearance, showName, showLevel, targetable, showCommandIcon, displayHandIcon, factionID int32
|
var randomizeAppearance, showName, showLevel, targetable, showCommandIcon, displayHandIcon, factionID int32
|
||||||
var name string
|
var name string
|
||||||
var level int8
|
var level int8
|
||||||
|
|
||||||
err := rows.Scan(&id, &name, &level, &maxLevel, &race, &modelType, &size,
|
err := rows.Scan(&id, &name, &level, &maxLevel, &race, &modelType, &size,
|
||||||
&hp, &power, &x, &y, &z, &heading, &respawnTime, &zoneID,
|
&hp, &power, &x, &y, &z, &heading, &respawnTime, &zoneID,
|
||||||
&aggroRadius, &aiStrategy, &lootTableID, &merchantType,
|
&aggroRadius, &aiStrategy, &lootTableID, &merchantType,
|
||||||
@ -511,7 +511,7 @@ func (wdb *WorldNPCDatabaseAdapter) LoadAllNPCs() ([]*npc.NPC, error) {
|
|||||||
fmt.Printf("Error scanning NPC row: %v\n", err)
|
fmt.Printf("Error scanning NPC row: %v\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set NPC properties
|
// Set NPC properties
|
||||||
newNPC.SetID(id)
|
newNPC.SetID(id)
|
||||||
newNPC.SetName(name)
|
newNPC.SetName(name)
|
||||||
@ -522,21 +522,21 @@ func (wdb *WorldNPCDatabaseAdapter) LoadAllNPCs() ([]*npc.NPC, error) {
|
|||||||
newNPC.SetY(y, false)
|
newNPC.SetY(y, false)
|
||||||
newNPC.SetZ(z)
|
newNPC.SetZ(z)
|
||||||
newNPC.SetHeadingFromFloat(heading)
|
newNPC.SetHeadingFromFloat(heading)
|
||||||
|
|
||||||
// Set additional properties if the NPC supports them
|
// Set additional properties if the NPC supports them
|
||||||
// TODO: Set additional properties like race, model type, etc.
|
// TODO: Set additional properties like race, model type, etc.
|
||||||
// This would require checking what methods are available on the NPC type
|
// This would require checking what methods are available on the NPC type
|
||||||
|
|
||||||
npcs = append(npcs, newNPC)
|
npcs = append(npcs, newNPC)
|
||||||
|
|
||||||
fmt.Printf("Loaded NPC: %s (ID: %d, Level: %d, Zone: %d)\n",
|
fmt.Printf("Loaded NPC: %s (ID: %d, Level: %d, Zone: %d)\n",
|
||||||
name, id, level, zoneID)
|
name, id, level, zoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return nil, fmt.Errorf("error iterating NPC rows: %w", err)
|
return nil, fmt.Errorf("error iterating NPC rows: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Successfully loaded %d NPCs from database\n", len(npcs))
|
fmt.Printf("Successfully loaded %d NPCs from database\n", len(npcs))
|
||||||
return npcs, nil
|
return npcs, nil
|
||||||
}
|
}
|
||||||
@ -546,7 +546,7 @@ func (wdb *WorldNPCDatabaseAdapter) SaveNPC(npcEntity *npc.NPC) error {
|
|||||||
if npcEntity == nil {
|
if npcEntity == nil {
|
||||||
return fmt.Errorf("cannot save nil NPC")
|
return fmt.Errorf("cannot save nil NPC")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract NPC properties for saving
|
// Extract NPC properties for saving
|
||||||
id := npcEntity.GetID()
|
id := npcEntity.GetID()
|
||||||
name := npcEntity.GetName()
|
name := npcEntity.GetName()
|
||||||
@ -556,24 +556,24 @@ func (wdb *WorldNPCDatabaseAdapter) SaveNPC(npcEntity *npc.NPC) error {
|
|||||||
y := npcEntity.GetY()
|
y := npcEntity.GetY()
|
||||||
z := npcEntity.GetZ()
|
z := npcEntity.GetZ()
|
||||||
heading := npcEntity.GetHeading()
|
heading := npcEntity.GetHeading()
|
||||||
|
|
||||||
// Insert or update NPC in database
|
// Insert or update NPC in database
|
||||||
_, err := wdb.db.Exec(`
|
_, err := wdb.db.Exec(`
|
||||||
INSERT OR REPLACE INTO npcs
|
INSERT OR REPLACE INTO npcs
|
||||||
(id, name, level, max_level, race, model_type, size, hp, power,
|
(id, name, level, max_level, race, model_type, size, hp, power,
|
||||||
x, y, z, heading, respawn_time, zone_id, aggro_radius, ai_strategy,
|
x, y, z, heading, respawn_time, zone_id, aggro_radius, ai_strategy,
|
||||||
loot_table_id, merchant_type, randomize_appearance, show_name,
|
loot_table_id, merchant_type, randomize_appearance, show_name,
|
||||||
show_level, targetable, show_command_icon, display_hand_icon, faction_id,
|
show_level, targetable, show_command_icon, display_hand_icon, faction_id,
|
||||||
created_date, last_modified)
|
created_date, last_modified)
|
||||||
VALUES
|
VALUES
|
||||||
(?, ?, ?, ?, 0, 0, 32, ?, ?, ?, ?, ?, ?, 300, 0, 10.0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
|
(?, ?, ?, ?, 0, 0, 32, ?, ?, ?, ?, ?, ?, 300, 0, 10.0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
|
||||||
strftime('%s', 'now'), strftime('%s', 'now'))
|
strftime('%s', 'now'), strftime('%s', 'now'))
|
||||||
`, id, name, level, level, hp, hp, x, y, z, heading)
|
`, id, name, level, level, hp, hp, x, y, z, heading)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save NPC %d: %w", id, err)
|
return fmt.Errorf("failed to save NPC %d: %w", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Saved NPC: %s (ID: %d) to database\n", name, id)
|
fmt.Printf("Saved NPC: %s (ID: %d) to database\n", name, id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -586,44 +586,44 @@ func (wdb *WorldNPCDatabaseAdapter) DeleteNPC(npcID int32) error {
|
|||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
// Delete related data first (due to foreign key constraints)
|
// Delete related data first (due to foreign key constraints)
|
||||||
_, err = tx.Exec("DELETE FROM npc_spells WHERE npc_id = ?", npcID)
|
_, err = tx.Exec("DELETE FROM npc_spells WHERE npc_id = ?", npcID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete NPC spells: %w", err)
|
return fmt.Errorf("failed to delete NPC spells: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM npc_skills WHERE npc_id = ?", npcID)
|
_, err = tx.Exec("DELETE FROM npc_skills WHERE npc_id = ?", npcID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete NPC skills: %w", err)
|
return fmt.Errorf("failed to delete NPC skills: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM npc_loot WHERE npc_id = ?", npcID)
|
_, err = tx.Exec("DELETE FROM npc_loot WHERE npc_id = ?", npcID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete NPC loot: %w", err)
|
return fmt.Errorf("failed to delete NPC loot: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the NPC itself
|
// Delete the NPC itself
|
||||||
result, err := tx.Exec("DELETE FROM npcs WHERE id = ?", npcID)
|
result, err := tx.Exec("DELETE FROM npcs WHERE id = ?", npcID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete NPC: %w", err)
|
return fmt.Errorf("failed to delete NPC: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if NPC was actually deleted
|
// Check if NPC was actually deleted
|
||||||
rowsAffected, err := result.RowsAffected()
|
rowsAffected, err := result.RowsAffected()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to check rows affected: %w", err)
|
return fmt.Errorf("failed to check rows affected: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rowsAffected == 0 {
|
if rowsAffected == 0 {
|
||||||
return fmt.Errorf("NPC %d not found", npcID)
|
return fmt.Errorf("NPC %d not found", npcID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit transaction
|
// Commit transaction
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Deleted NPC %d from database\n", npcID)
|
fmt.Printf("Deleted NPC %d from database\n", npcID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -640,19 +640,19 @@ func (wdb *WorldNPCDatabaseAdapter) LoadNPCSpells(npcID int32) ([]*npc.NPCSpell,
|
|||||||
return nil, fmt.Errorf("failed to query NPC spells: %w", err)
|
return nil, fmt.Errorf("failed to query NPC spells: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var spells []*npc.NPCSpell
|
var spells []*npc.NPCSpell
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
spell := npc.NewNPCSpell()
|
spell := npc.NewNPCSpell()
|
||||||
var npcIDDB, spellID, tier, hpPercentage, priority, castType, recastDelay int32
|
var npcIDDB, spellID, tier, hpPercentage, priority, castType, recastDelay int32
|
||||||
|
|
||||||
err := rows.Scan(&npcIDDB, &spellID, &tier, &hpPercentage, &priority, &castType, &recastDelay)
|
err := rows.Scan(&npcIDDB, &spellID, &tier, &hpPercentage, &priority, &castType, &recastDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error scanning NPC spell row: %v\n", err)
|
fmt.Printf("Error scanning NPC spell row: %v\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set spell properties (using available methods from NPCSpell)
|
// Set spell properties (using available methods from NPCSpell)
|
||||||
// TODO: Set spell properties based on what methods are available
|
// TODO: Set spell properties based on what methods are available
|
||||||
// spell.SetSpellID(spellID)
|
// spell.SetSpellID(spellID)
|
||||||
@ -661,14 +661,14 @@ func (wdb *WorldNPCDatabaseAdapter) LoadNPCSpells(npcID int32) ([]*npc.NPCSpell,
|
|||||||
// spell.SetPriority(priority)
|
// spell.SetPriority(priority)
|
||||||
// spell.SetCastType(castType)
|
// spell.SetCastType(castType)
|
||||||
// spell.SetRecastDelay(recastDelay)
|
// spell.SetRecastDelay(recastDelay)
|
||||||
|
|
||||||
spells = append(spells, spell)
|
spells = append(spells, spell)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return nil, fmt.Errorf("error iterating NPC spell rows: %w", err)
|
return nil, fmt.Errorf("error iterating NPC spell rows: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return spells, nil
|
return spells, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -680,37 +680,37 @@ func (wdb *WorldNPCDatabaseAdapter) SaveNPCSpells(npcID int32, spells []*npc.NPC
|
|||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
// Delete existing spells for this NPC
|
// Delete existing spells for this NPC
|
||||||
_, err = tx.Exec("DELETE FROM npc_spells WHERE npc_id = ?", npcID)
|
_, err = tx.Exec("DELETE FROM npc_spells WHERE npc_id = ?", npcID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete existing NPC spells: %w", err)
|
return fmt.Errorf("failed to delete existing NPC spells: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert new spells
|
// Insert new spells
|
||||||
for _, spell := range spells {
|
for _, spell := range spells {
|
||||||
if spell == nil {
|
if spell == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get spell properties from NPCSpell object
|
// TODO: Get spell properties from NPCSpell object
|
||||||
// For now, use placeholder values
|
// For now, use placeholder values
|
||||||
_, err = tx.Exec(`
|
_, err = tx.Exec(`
|
||||||
INSERT INTO npc_spells
|
INSERT INTO npc_spells
|
||||||
(npc_id, spell_id, tier, hp_percentage, priority, cast_type, recast_delay)
|
(npc_id, spell_id, tier, hp_percentage, priority, cast_type, recast_delay)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
`, npcID, 1, 1, 100, 1, 0, 5) // Placeholder values
|
`, npcID, 1, 1, 100, 1, 0, 5) // Placeholder values
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save NPC spell: %w", err)
|
return fmt.Errorf("failed to save NPC spell: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit transaction
|
// Commit transaction
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Saved %d spells for NPC %d\n", len(spells), npcID)
|
fmt.Printf("Saved %d spells for NPC %d\n", len(spells), npcID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -727,29 +727,29 @@ func (wdb *WorldNPCDatabaseAdapter) LoadNPCSkills(npcID int32) (map[string]*npc.
|
|||||||
return nil, fmt.Errorf("failed to query NPC skills: %w", err)
|
return nil, fmt.Errorf("failed to query NPC skills: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
skills := make(map[string]*npc.Skill)
|
skills := make(map[string]*npc.Skill)
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var npcIDDB, skillValue, maxValue int32
|
var npcIDDB, skillValue, maxValue int32
|
||||||
var skillName string
|
var skillName string
|
||||||
|
|
||||||
err := rows.Scan(&npcIDDB, &skillName, &skillValue, &maxValue)
|
err := rows.Scan(&npcIDDB, &skillName, &skillValue, &maxValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error scanning NPC skill row: %v\n", err)
|
fmt.Printf("Error scanning NPC skill row: %v\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create skill object
|
// Create skill object
|
||||||
skill := npc.NewSkill(0, skillName, int16(skillValue), int16(maxValue))
|
skill := npc.NewSkill(0, skillName, int16(skillValue), int16(maxValue))
|
||||||
|
|
||||||
skills[skillName] = skill
|
skills[skillName] = skill
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return nil, fmt.Errorf("error iterating NPC skill rows: %w", err)
|
return nil, fmt.Errorf("error iterating NPC skill rows: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return skills, nil
|
return skills, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -761,40 +761,40 @@ func (wdb *WorldNPCDatabaseAdapter) SaveNPCSkills(npcID int32, skills map[string
|
|||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
// Delete existing skills for this NPC
|
// Delete existing skills for this NPC
|
||||||
_, err = tx.Exec("DELETE FROM npc_skills WHERE npc_id = ?", npcID)
|
_, err = tx.Exec("DELETE FROM npc_skills WHERE npc_id = ?", npcID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete existing NPC skills: %w", err)
|
return fmt.Errorf("failed to delete existing NPC skills: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert new skills
|
// Insert new skills
|
||||||
for skillName, skill := range skills {
|
for skillName, skill := range skills {
|
||||||
if skill == nil {
|
if skill == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get skill values (need to access directly since GetMaxVal doesn't exist)
|
// Get skill values (need to access directly since GetMaxVal doesn't exist)
|
||||||
currentVal := skill.GetCurrentVal()
|
currentVal := skill.GetCurrentVal()
|
||||||
// TODO: Add GetMaxVal method to Skill struct or access MaxVal field
|
// TODO: Add GetMaxVal method to Skill struct or access MaxVal field
|
||||||
maxVal := int16(100) // Placeholder - should be skill.MaxVal when accessible
|
maxVal := int16(100) // Placeholder - should be skill.MaxVal when accessible
|
||||||
|
|
||||||
_, err = tx.Exec(`
|
_, err = tx.Exec(`
|
||||||
INSERT INTO npc_skills
|
INSERT INTO npc_skills
|
||||||
(npc_id, skill_name, skill_value, max_value)
|
(npc_id, skill_name, skill_value, max_value)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
`, npcID, skillName, currentVal, maxVal)
|
`, npcID, skillName, currentVal, maxVal)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save NPC skill %s: %w", skillName, err)
|
return fmt.Errorf("failed to save NPC skill %s: %w", skillName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit transaction
|
// Commit transaction
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Saved %d skills for NPC %d\n", len(skills), npcID)
|
fmt.Printf("Saved %d skills for NPC %d\n", len(skills), npcID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -820,4 +820,4 @@ func (wl *WorldNPCLoggerAdapter) LogDebug(message string, args ...any) {
|
|||||||
// LogWarning implements npc.Logger interface
|
// LogWarning implements npc.Logger interface
|
||||||
func (wl *WorldNPCLoggerAdapter) LogWarning(message string, args ...any) {
|
func (wl *WorldNPCLoggerAdapter) LogWarning(message string, args ...any) {
|
||||||
fmt.Printf("[NPC] WARNING: "+message+"\n", args...)
|
fmt.Printf("[NPC] WARNING: "+message+"\n", args...)
|
||||||
}
|
}
|
||||||
|
@ -8,47 +8,47 @@ import (
|
|||||||
// RegisterPacketHandlers registers all world server packet handlers
|
// RegisterPacketHandlers registers all world server packet handlers
|
||||||
func (w *World) RegisterPacketHandlers() {
|
func (w *World) RegisterPacketHandlers() {
|
||||||
fmt.Println("Registering world server packet handlers...")
|
fmt.Println("Registering world server packet handlers...")
|
||||||
|
|
||||||
// Basic connection and loading handlers
|
// Basic connection and loading handlers
|
||||||
packets.RegisterGlobalHandler(packets.OP_DoneLoadingZoneResourcesMsg, w.HandleDoneLoadingZoneResources)
|
packets.RegisterGlobalHandler(packets.OP_DoneLoadingZoneResourcesMsg, w.HandleDoneLoadingZoneResources)
|
||||||
packets.RegisterGlobalHandler(packets.OP_DoneSendingInitialEntitiesMsg, w.HandleDoneSendingInitialEntities)
|
packets.RegisterGlobalHandler(packets.OP_DoneSendingInitialEntitiesMsg, w.HandleDoneSendingInitialEntities)
|
||||||
packets.RegisterGlobalHandler(packets.OP_DoneLoadingEntityResourcesMsg, w.HandleDoneLoadingEntityResources)
|
packets.RegisterGlobalHandler(packets.OP_DoneLoadingEntityResourcesMsg, w.HandleDoneLoadingEntityResources)
|
||||||
packets.RegisterGlobalHandler(packets.OP_DoneLoadingUIResourcesMsg, w.HandleDoneLoadingUIResources)
|
packets.RegisterGlobalHandler(packets.OP_DoneLoadingUIResourcesMsg, w.HandleDoneLoadingUIResources)
|
||||||
|
|
||||||
// Zone readiness
|
// Zone readiness
|
||||||
packets.RegisterGlobalHandler(packets.OP_ReadyToZoneMsg, w.HandleReadyToZone)
|
packets.RegisterGlobalHandler(packets.OP_ReadyToZoneMsg, w.HandleReadyToZone)
|
||||||
|
|
||||||
// Command handling
|
// Command handling
|
||||||
packets.RegisterGlobalHandler(packets.OP_ClientCmdMsg, w.HandleClientCommand)
|
packets.RegisterGlobalHandler(packets.OP_ClientCmdMsg, w.HandleClientCommand)
|
||||||
packets.RegisterGlobalHandler(packets.OP_DispatchClientCmdMsg, w.HandleDispatchClientCommand)
|
packets.RegisterGlobalHandler(packets.OP_DispatchClientCmdMsg, w.HandleDispatchClientCommand)
|
||||||
|
|
||||||
// Position updates
|
// Position updates
|
||||||
packets.RegisterGlobalHandler(packets.OP_UpdatePositionMsg, w.HandlePositionUpdate)
|
packets.RegisterGlobalHandler(packets.OP_UpdatePositionMsg, w.HandlePositionUpdate)
|
||||||
|
|
||||||
// Chat system
|
// Chat system
|
||||||
packets.RegisterGlobalHandler(packets.OP_ChatTellChannelMsg, w.HandleChatTellChannel)
|
packets.RegisterGlobalHandler(packets.OP_ChatTellChannelMsg, w.HandleChatTellChannel)
|
||||||
packets.RegisterGlobalHandler(packets.OP_ChatTellUserMsg, w.HandleChatTellUser)
|
packets.RegisterGlobalHandler(packets.OP_ChatTellUserMsg, w.HandleChatTellUser)
|
||||||
|
|
||||||
// Zone transitions
|
// Zone transitions
|
||||||
packets.RegisterGlobalHandler(packets.OP_ChangeZoneMsg, w.HandleChangeZone)
|
packets.RegisterGlobalHandler(packets.OP_ChangeZoneMsg, w.HandleChangeZone)
|
||||||
packets.RegisterGlobalHandler(packets.OP_ClientTeleportRequestMsg, w.HandleClientTeleportRequest)
|
packets.RegisterGlobalHandler(packets.OP_ClientTeleportRequestMsg, w.HandleClientTeleportRequest)
|
||||||
|
|
||||||
// Achievement system
|
// Achievement system
|
||||||
packets.RegisterGlobalHandler(packets.OP_AchievementUpdateMsg, w.HandleAchievementUpdate)
|
packets.RegisterGlobalHandler(packets.OP_AchievementUpdateMsg, w.HandleAchievementUpdate)
|
||||||
packets.RegisterGlobalHandler(packets.OP_CharacterAchievements, w.HandleCharacterAchievements)
|
packets.RegisterGlobalHandler(packets.OP_CharacterAchievements, w.HandleCharacterAchievements)
|
||||||
|
|
||||||
// Title system
|
// Title system
|
||||||
packets.RegisterGlobalHandler(packets.OP_TitleUpdateMsg, w.HandleTitleUpdate)
|
packets.RegisterGlobalHandler(packets.OP_TitleUpdateMsg, w.HandleTitleUpdate)
|
||||||
packets.RegisterGlobalHandler(packets.OP_CharacterTitles, w.HandleCharacterTitles)
|
packets.RegisterGlobalHandler(packets.OP_CharacterTitles, w.HandleCharacterTitles)
|
||||||
packets.RegisterGlobalHandler(packets.OP_SetActiveTitleMsg, w.HandleSetActiveTitle)
|
packets.RegisterGlobalHandler(packets.OP_SetActiveTitleMsg, w.HandleSetActiveTitle)
|
||||||
|
|
||||||
// NPC system
|
// NPC system
|
||||||
packets.RegisterGlobalHandler(packets.OP_NPCAttackMsg, w.HandleNPCAttack)
|
packets.RegisterGlobalHandler(packets.OP_NPCAttackMsg, w.HandleNPCAttack)
|
||||||
packets.RegisterGlobalHandler(packets.OP_NPCTargetMsg, w.HandleNPCTarget)
|
packets.RegisterGlobalHandler(packets.OP_NPCTargetMsg, w.HandleNPCTarget)
|
||||||
packets.RegisterGlobalHandler(packets.OP_NPCInfoMsg, w.HandleNPCInfo)
|
packets.RegisterGlobalHandler(packets.OP_NPCInfoMsg, w.HandleNPCInfo)
|
||||||
packets.RegisterGlobalHandler(packets.OP_NPCSpellCastMsg, w.HandleNPCSpellCast)
|
packets.RegisterGlobalHandler(packets.OP_NPCSpellCastMsg, w.HandleNPCSpellCast)
|
||||||
packets.RegisterGlobalHandler(packets.OP_NPCMovementMsg, w.HandleNPCMovement)
|
packets.RegisterGlobalHandler(packets.OP_NPCMovementMsg, w.HandleNPCMovement)
|
||||||
|
|
||||||
// Item system
|
// Item system
|
||||||
packets.RegisterGlobalHandler(packets.OP_ItemMoveMsg, w.HandleItemMove)
|
packets.RegisterGlobalHandler(packets.OP_ItemMoveMsg, w.HandleItemMove)
|
||||||
packets.RegisterGlobalHandler(packets.OP_ItemEquipMsg, w.HandleItemEquip)
|
packets.RegisterGlobalHandler(packets.OP_ItemEquipMsg, w.HandleItemEquip)
|
||||||
@ -57,77 +57,77 @@ func (w *World) RegisterPacketHandlers() {
|
|||||||
packets.RegisterGlobalHandler(packets.OP_ItemDropMsg, w.HandleItemDrop)
|
packets.RegisterGlobalHandler(packets.OP_ItemDropMsg, w.HandleItemDrop)
|
||||||
packets.RegisterGlobalHandler(packets.OP_ItemExamineMsg, w.HandleItemExamine)
|
packets.RegisterGlobalHandler(packets.OP_ItemExamineMsg, w.HandleItemExamine)
|
||||||
packets.RegisterGlobalHandler(packets.OP_ItemUpdateMsg, w.HandleItemUpdate)
|
packets.RegisterGlobalHandler(packets.OP_ItemUpdateMsg, w.HandleItemUpdate)
|
||||||
|
|
||||||
fmt.Printf("Registered %d packet handlers\n", 28)
|
fmt.Printf("Registered %d packet handlers\n", 28)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleDoneLoadingZoneResources handles when client finishes loading zone resources
|
// HandleDoneLoadingZoneResources handles when client finishes loading zone resources
|
||||||
func (w *World) HandleDoneLoadingZoneResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleDoneLoadingZoneResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s finished loading zone resources\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s finished loading zone resources\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
// Update client state
|
// Update client state
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
// TODO: Send initial zone data, spawns, etc.
|
// TODO: Send initial zone data, spawns, etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleDoneSendingInitialEntities handles when client finishes receiving initial entities
|
// HandleDoneSendingInitialEntities handles when client finishes receiving initial entities
|
||||||
func (w *World) HandleDoneSendingInitialEntities(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleDoneSendingInitialEntities(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s finished receiving initial entities\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s finished receiving initial entities\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
// TODO: Mark client as fully loaded
|
// TODO: Mark client as fully loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleDoneLoadingEntityResources handles when client finishes loading entity resources
|
// HandleDoneLoadingEntityResources handles when client finishes loading entity resources
|
||||||
func (w *World) HandleDoneLoadingEntityResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleDoneLoadingEntityResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s finished loading entity resources\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s finished loading entity resources\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleDoneLoadingUIResources handles when client finishes loading UI resources
|
// HandleDoneLoadingUIResources handles when client finishes loading UI resources
|
||||||
func (w *World) HandleDoneLoadingUIResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleDoneLoadingUIResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s finished loading UI resources\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s finished loading UI resources\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
// TODO: Send initial UI packets (character sheet, spellbook, etc.)
|
// TODO: Send initial UI packets (character sheet, spellbook, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleReadyToZone handles when client is ready to enter the zone
|
// HandleReadyToZone handles when client is ready to enter the zone
|
||||||
func (w *World) HandleReadyToZone(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleReadyToZone(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s is ready to enter zone\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s is ready to enter zone\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Complete zone entry process
|
// TODO: Complete zone entry process
|
||||||
// - Send world time
|
// - Send world time
|
||||||
// - Send MOTD
|
// - Send MOTD
|
||||||
// - Send initial game state
|
// - Send initial game state
|
||||||
// - Add player to zone
|
// - Add player to zone
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,28 +136,28 @@ func (w *World) HandleClientCommand(ctx *packets.PacketContext, packet *packets.
|
|||||||
// TODO: Parse command from packet data
|
// TODO: Parse command from packet data
|
||||||
// For now, just log the attempt
|
// For now, just log the attempt
|
||||||
fmt.Printf("Client %s sent command (raw packet)\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent command (raw packet)\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Extract command text and dispatch to command manager
|
// TODO: Extract command text and dispatch to command manager
|
||||||
// This will require parsing the packet structure
|
// This will require parsing the packet structure
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleDispatchClientCommand handles dispatched client commands
|
// HandleDispatchClientCommand handles dispatched client commands
|
||||||
func (w *World) HandleDispatchClientCommand(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleDispatchClientCommand(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent dispatched command\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent dispatched command\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
// TODO: Handle dispatched commands
|
// TODO: Handle dispatched commands
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,106 +167,106 @@ func (w *World) HandlePositionUpdate(ctx *packets.PacketContext, packet *packets
|
|||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse position data from packet
|
// TODO: Parse position data from packet
|
||||||
// TODO: Update player position in zone
|
// TODO: Update player position in zone
|
||||||
// TODO: Send position update to other players in range
|
// TODO: Send position update to other players in range
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleChatTellChannel handles channel chat messages
|
// HandleChatTellChannel handles channel chat messages
|
||||||
func (w *World) HandleChatTellChannel(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleChatTellChannel(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent channel chat message\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent channel chat message\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse chat message from packet
|
// TODO: Parse chat message from packet
|
||||||
// TODO: Validate channel permissions
|
// TODO: Validate channel permissions
|
||||||
// TODO: Broadcast message to appropriate recipients
|
// TODO: Broadcast message to appropriate recipients
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleChatTellUser handles direct tell messages
|
// HandleChatTellUser handles direct tell messages
|
||||||
func (w *World) HandleChatTellUser(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleChatTellUser(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent tell message\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent tell message\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse tell message and target from packet
|
// TODO: Parse tell message and target from packet
|
||||||
// TODO: Find target player
|
// TODO: Find target player
|
||||||
// TODO: Send message to target
|
// TODO: Send message to target
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleChangeZone handles zone change requests
|
// HandleChangeZone handles zone change requests
|
||||||
func (w *World) HandleChangeZone(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleChangeZone(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s requested zone change\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s requested zone change\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse zone change request from packet
|
// TODO: Parse zone change request from packet
|
||||||
// TODO: Validate zone change is allowed
|
// TODO: Validate zone change is allowed
|
||||||
// TODO: Begin zone transfer process
|
// TODO: Begin zone transfer process
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleClientTeleportRequest handles client teleport requests
|
// HandleClientTeleportRequest handles client teleport requests
|
||||||
func (w *World) HandleClientTeleportRequest(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleClientTeleportRequest(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s requested teleport\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s requested teleport\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse teleport request from packet
|
// TODO: Parse teleport request from packet
|
||||||
// TODO: Validate teleport permissions
|
// TODO: Validate teleport permissions
|
||||||
// TODO: Execute teleport
|
// TODO: Execute teleport
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleAchievementUpdate handles achievement update requests from client
|
// HandleAchievementUpdate handles achievement update requests from client
|
||||||
func (w *World) HandleAchievementUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleAchievementUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s requested achievement update\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s requested achievement update\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// Send current achievement data to client
|
// Send current achievement data to client
|
||||||
w.SendAchievementData(client)
|
w.SendAchievementData(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleCharacterAchievements handles character achievements request from client
|
// HandleCharacterAchievements handles character achievements request from client
|
||||||
func (w *World) HandleCharacterAchievements(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleCharacterAchievements(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s requested character achievements\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s requested character achievements\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// Send complete achievement list to client
|
// Send complete achievement list to client
|
||||||
w.SendCharacterAchievements(client)
|
w.SendCharacterAchievements(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,22 +275,22 @@ func (w *World) SendAchievementData(client *Client) {
|
|||||||
if w.achievementMgr == nil {
|
if w.achievementMgr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
characterID := client.CharacterID
|
characterID := client.CharacterID
|
||||||
|
|
||||||
// Get player's completed achievements
|
// Get player's completed achievements
|
||||||
completedAchievements := w.achievementMgr.GetPlayerCompletedAchievements(characterID)
|
completedAchievements := w.achievementMgr.GetPlayerCompletedAchievements(characterID)
|
||||||
inProgressAchievements := w.achievementMgr.GetPlayerInProgressAchievements(characterID)
|
inProgressAchievements := w.achievementMgr.GetPlayerInProgressAchievements(characterID)
|
||||||
|
|
||||||
fmt.Printf("Sending achievement data to %s: %d completed, %d in progress\n",
|
fmt.Printf("Sending achievement data to %s: %d completed, %d in progress\n",
|
||||||
client.CharacterName, len(completedAchievements), len(inProgressAchievements))
|
client.CharacterName, len(completedAchievements), len(inProgressAchievements))
|
||||||
|
|
||||||
// Create achievement update packet
|
// Create achievement update packet
|
||||||
// This would normally build a proper packet structure
|
// This would normally build a proper packet structure
|
||||||
totalPoints := w.calculateAchievementPoints(characterID)
|
totalPoints := w.calculateAchievementPoints(characterID)
|
||||||
|
|
||||||
// Send packet to client (placeholder - would use actual packet building)
|
// Send packet to client (placeholder - would use actual packet building)
|
||||||
client.SendSimpleMessage(fmt.Sprintf("Achievement Update: %d completed, %d in progress, %d points",
|
client.SendSimpleMessage(fmt.Sprintf("Achievement Update: %d completed, %d in progress, %d points",
|
||||||
len(completedAchievements), len(inProgressAchievements), totalPoints))
|
len(completedAchievements), len(inProgressAchievements), totalPoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,19 +299,19 @@ func (w *World) SendCharacterAchievements(client *Client) {
|
|||||||
if w.achievementMgr == nil {
|
if w.achievementMgr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
characterID := client.CharacterID
|
characterID := client.CharacterID
|
||||||
|
|
||||||
// Get all achievements with player progress
|
// Get all achievements with player progress
|
||||||
allAchievements := w.achievementMgr.masterList.GetAllAchievements()
|
allAchievements := w.achievementMgr.masterList.GetAllAchievements()
|
||||||
characterData := make(map[string]interface{})
|
characterData := make(map[string]any)
|
||||||
|
|
||||||
for achievementID, achievement := range allAchievements {
|
for achievementID, achievement := range allAchievements {
|
||||||
progress := w.achievementMgr.GetPlayerProgress(characterID, achievementID)
|
progress := w.achievementMgr.GetPlayerProgress(characterID, achievementID)
|
||||||
completed := w.achievementMgr.IsPlayerCompleted(characterID, achievementID)
|
completed := w.achievementMgr.IsPlayerCompleted(characterID, achievementID)
|
||||||
percentage := w.achievementMgr.GetCompletionPercentage(characterID, achievementID)
|
percentage := w.achievementMgr.GetCompletionPercentage(characterID, achievementID)
|
||||||
|
|
||||||
characterData[fmt.Sprintf("achievement_%d", achievementID)] = map[string]interface{}{
|
characterData[fmt.Sprintf("achievement_%d", achievementID)] = map[string]any{
|
||||||
"id": achievementID,
|
"id": achievementID,
|
||||||
"title": achievement.Title,
|
"title": achievement.Title,
|
||||||
"description": achievement.UncompletedText,
|
"description": achievement.UncompletedText,
|
||||||
@ -324,10 +324,10 @@ func (w *World) SendCharacterAchievements(client *Client) {
|
|||||||
"expansion": achievement.Expansion,
|
"expansion": achievement.Expansion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Sending complete achievement list to %s: %d achievements\n",
|
fmt.Printf("Sending complete achievement list to %s: %d achievements\n",
|
||||||
client.CharacterName, len(allAchievements))
|
client.CharacterName, len(allAchievements))
|
||||||
|
|
||||||
// Send packet to client (placeholder - would use actual packet building)
|
// Send packet to client (placeholder - would use actual packet building)
|
||||||
client.SendSimpleMessage(fmt.Sprintf("Character Achievements: %d total achievements", len(allAchievements)))
|
client.SendSimpleMessage(fmt.Sprintf("Character Achievements: %d total achievements", len(allAchievements)))
|
||||||
}
|
}
|
||||||
@ -337,67 +337,67 @@ func (w *World) calculateAchievementPoints(characterID int32) uint32 {
|
|||||||
if w.achievementMgr == nil {
|
if w.achievementMgr == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
completedAchievements := w.achievementMgr.GetPlayerCompletedAchievements(characterID)
|
completedAchievements := w.achievementMgr.GetPlayerCompletedAchievements(characterID)
|
||||||
totalPoints := uint32(0)
|
totalPoints := uint32(0)
|
||||||
|
|
||||||
for _, achievementID := range completedAchievements {
|
for _, achievementID := range completedAchievements {
|
||||||
achievement := w.achievementMgr.GetAchievement(achievementID)
|
achievement := w.achievementMgr.GetAchievement(achievementID)
|
||||||
if achievement != nil {
|
if achievement != nil {
|
||||||
totalPoints += achievement.PointValue
|
totalPoints += achievement.PointValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalPoints
|
return totalPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleTitleUpdate handles title update requests from client
|
// HandleTitleUpdate handles title update requests from client
|
||||||
func (w *World) HandleTitleUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleTitleUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s requested title update\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s requested title update\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// Send current title data to client
|
// Send current title data to client
|
||||||
w.SendTitleData(client)
|
w.SendTitleData(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleCharacterTitles handles character titles request from client
|
// HandleCharacterTitles handles character titles request from client
|
||||||
func (w *World) HandleCharacterTitles(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleCharacterTitles(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s requested character titles\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s requested character titles\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// Send complete title list to client
|
// Send complete title list to client
|
||||||
w.SendCharacterTitles(client)
|
w.SendCharacterTitles(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSetActiveTitle handles setting active title requests from client
|
// HandleSetActiveTitle handles setting active title requests from client
|
||||||
func (w *World) HandleSetActiveTitle(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleSetActiveTitle(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s requested to set active title\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s requested to set active title\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse title ID and position from packet data
|
// TODO: Parse title ID and position from packet data
|
||||||
// TODO: Validate player has the title
|
// TODO: Validate player has the title
|
||||||
// TODO: Set active title
|
// TODO: Set active title
|
||||||
// TODO: Send confirmation to client
|
// TODO: Send confirmation to client
|
||||||
|
|
||||||
// For now, just log the request
|
// For now, just log the request
|
||||||
fmt.Printf("Set active title request for %s processed\n", client.CharacterName)
|
fmt.Printf("Set active title request for %s processed\n", client.CharacterName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,16 +406,16 @@ func (w *World) SendTitleData(client *Client) {
|
|||||||
if w.titleMgr == nil {
|
if w.titleMgr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
characterID := client.CharacterID
|
characterID := client.CharacterID
|
||||||
|
|
||||||
// Get player's titles
|
// Get player's titles
|
||||||
playerTitles := w.titleMgr.GetPlayerTitles(characterID)
|
playerTitles := w.titleMgr.GetPlayerTitles(characterID)
|
||||||
titleCount := playerTitles.GetTitleCount()
|
titleCount := playerTitles.GetTitleCount()
|
||||||
|
|
||||||
fmt.Printf("Sending title data to %s: %d titles\n",
|
fmt.Printf("Sending title data to %s: %d titles\n",
|
||||||
client.CharacterName, titleCount)
|
client.CharacterName, titleCount)
|
||||||
|
|
||||||
// Create title update packet (placeholder)
|
// Create title update packet (placeholder)
|
||||||
client.SendSimpleMessage(fmt.Sprintf("Title Update: %d titles available", titleCount))
|
client.SendSimpleMessage(fmt.Sprintf("Title Update: %d titles available", titleCount))
|
||||||
}
|
}
|
||||||
@ -425,24 +425,24 @@ func (w *World) SendCharacterTitles(client *Client) {
|
|||||||
if w.titleMgr == nil {
|
if w.titleMgr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
characterID := client.CharacterID
|
characterID := client.CharacterID
|
||||||
|
|
||||||
// Get player's titles and master list
|
// Get player's titles and master list
|
||||||
playerTitles := w.titleMgr.GetPlayerTitles(characterID)
|
playerTitles := w.titleMgr.GetPlayerTitles(characterID)
|
||||||
masterList := w.titleMgr.titleManager.GetMasterList()
|
masterList := w.titleMgr.titleManager.GetMasterList()
|
||||||
|
|
||||||
titleCount := playerTitles.GetTitleCount()
|
titleCount := playerTitles.GetTitleCount()
|
||||||
totalTitles := masterList.GetTitleCount()
|
totalTitles := masterList.GetTitleCount()
|
||||||
|
|
||||||
fmt.Printf("Sending complete title list to %s: %d owned out of %d total\n",
|
fmt.Printf("Sending complete title list to %s: %d owned out of %d total\n",
|
||||||
client.CharacterName, titleCount, totalTitles)
|
client.CharacterName, titleCount, totalTitles)
|
||||||
|
|
||||||
// Get player's formatted name with titles
|
// Get player's formatted name with titles
|
||||||
formattedName := w.titleMgr.GetPlayerFormattedName(characterID, client.CharacterName)
|
formattedName := w.titleMgr.GetPlayerFormattedName(characterID, client.CharacterName)
|
||||||
|
|
||||||
// Send packet to client (placeholder - would use actual packet building)
|
// Send packet to client (placeholder - would use actual packet building)
|
||||||
client.SendSimpleMessage(fmt.Sprintf("Character Titles: %d owned, %d total. Display name: %s",
|
client.SendSimpleMessage(fmt.Sprintf("Character Titles: %d owned, %d total. Display name: %s",
|
||||||
titleCount, totalTitles, formattedName))
|
titleCount, totalTitles, formattedName))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,77 +451,77 @@ func (w *World) SendCharacterTitles(client *Client) {
|
|||||||
// HandleNPCAttack handles NPC attack packets from clients
|
// HandleNPCAttack handles NPC attack packets from clients
|
||||||
func (w *World) HandleNPCAttack(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleNPCAttack(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent NPC attack packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent NPC attack packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse NPC ID and attack type from packet data
|
// TODO: Parse NPC ID and attack type from packet data
|
||||||
// TODO: Validate player can attack NPC
|
// TODO: Validate player can attack NPC
|
||||||
// TODO: Process attack through combat system
|
// TODO: Process attack through combat system
|
||||||
// TODO: Send attack result to client and nearby players
|
// TODO: Send attack result to client and nearby players
|
||||||
|
|
||||||
// For now, just trigger a test NPC kill event for achievement testing
|
// For now, just trigger a test NPC kill event for achievement testing
|
||||||
if w.npcMgr != nil {
|
if w.npcMgr != nil {
|
||||||
testNPCID := int32(1001)
|
testNPCID := int32(1001)
|
||||||
w.npcMgr.OnNPCKilled(testNPCID, ctx.Client.GetCharacterID())
|
w.npcMgr.OnNPCKilled(testNPCID, ctx.Client.GetCharacterID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleNPCTarget handles NPC targeting packets from clients
|
// HandleNPCTarget handles NPC targeting packets from clients
|
||||||
func (w *World) HandleNPCTarget(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleNPCTarget(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent NPC target packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent NPC target packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse NPC ID from packet data
|
// TODO: Parse NPC ID from packet data
|
||||||
// TODO: Validate NPC exists and is targetable
|
// TODO: Validate NPC exists and is targetable
|
||||||
// TODO: Set player's target
|
// TODO: Set player's target
|
||||||
// TODO: Send targeting confirmation to client
|
// TODO: Send targeting confirmation to client
|
||||||
|
|
||||||
// For testing, send NPC info for any targeting
|
// For testing, send NPC info for any targeting
|
||||||
w.SendNPCInfo(client, 1001) // Test NPC
|
w.SendNPCInfo(client, 1001) // Test NPC
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleNPCInfo handles NPC info requests from clients
|
// HandleNPCInfo handles NPC info requests from clients
|
||||||
func (w *World) HandleNPCInfo(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleNPCInfo(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s requested NPC info\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s requested NPC info\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse NPC ID from packet data
|
// TODO: Parse NPC ID from packet data
|
||||||
// TODO: Send NPC information to client
|
// TODO: Send NPC information to client
|
||||||
|
|
||||||
// For testing, send test NPC info
|
// For testing, send test NPC info
|
||||||
w.SendNPCInfo(client, 1001)
|
w.SendNPCInfo(client, 1001)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleNPCSpellCast handles NPC spell cast notifications
|
// HandleNPCSpellCast handles NPC spell cast notifications
|
||||||
func (w *World) HandleNPCSpellCast(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleNPCSpellCast(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s received NPC spell cast notification\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s received NPC spell cast notification\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse spell cast data from packet
|
// TODO: Parse spell cast data from packet
|
||||||
// TODO: Process spell effects
|
// TODO: Process spell effects
|
||||||
// TODO: Update client state based on spell effects
|
// TODO: Update client state based on spell effects
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,12 +531,12 @@ func (w *World) HandleNPCMovement(ctx *packets.PacketContext, packet *packets.Pa
|
|||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse NPC movement data from packet
|
// TODO: Parse NPC movement data from packet
|
||||||
// TODO: Update NPC position in world
|
// TODO: Update NPC position in world
|
||||||
// TODO: Send movement update to other clients in range
|
// TODO: Send movement update to other clients in range
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,31 +545,31 @@ func (w *World) SendNPCInfo(client *Client, npcID int32) {
|
|||||||
if w.npcMgr == nil {
|
if w.npcMgr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get NPC information
|
// Get NPC information
|
||||||
npcInfo := w.npcMgr.GetNPCInfo(npcID)
|
npcInfo := w.npcMgr.GetNPCInfo(npcID)
|
||||||
if npcInfo == nil {
|
if npcInfo == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Sending NPC info to %s: NPC %d (%s) Level %d\n",
|
fmt.Printf("Sending NPC info to %s: NPC %d (%s) Level %d\n",
|
||||||
client.CharacterName, npcInfo.ID, npcInfo.Name, npcInfo.Level)
|
client.CharacterName, npcInfo.ID, npcInfo.Name, npcInfo.Level)
|
||||||
|
|
||||||
// Get NPC statistics for additional info
|
// Get NPC statistics for additional info
|
||||||
stats := w.npcMgr.GetStatistics()
|
stats := w.npcMgr.GetStatistics()
|
||||||
|
|
||||||
// Create NPC info packet (placeholder)
|
// Create NPC info packet (placeholder)
|
||||||
client.SendSimpleMessage(fmt.Sprintf("NPC Info: %s (ID: %d, Level: %d) - %v total NPCs active",
|
client.SendSimpleMessage(fmt.Sprintf("NPC Info: %s (ID: %d, Level: %d) - %v total NPCs active",
|
||||||
npcInfo.Name, npcInfo.ID, npcInfo.Level, stats["total_npcs"]))
|
npcInfo.Name, npcInfo.ID, npcInfo.Level, stats["total_npcs"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNPCUpdate sends NPC update to clients in range
|
// SendNPCUpdate sends NPC update to clients in range
|
||||||
func (w *World) SendNPCUpdate(npcID int32, updateType string, data map[string]interface{}) {
|
func (w *World) SendNPCUpdate(npcID int32, updateType string, data map[string]any) {
|
||||||
// TODO: Implement NPC update broadcasting
|
// TODO: Implement NPC update broadcasting
|
||||||
// This would send updates to all clients in range of the NPC
|
// This would send updates to all clients in range of the NPC
|
||||||
|
|
||||||
fmt.Printf("NPC Update: NPC %d - %s: %v\n", npcID, updateType, data)
|
fmt.Printf("NPC Update: NPC %d - %s: %v\n", npcID, updateType, data)
|
||||||
|
|
||||||
// Get all clients and send update (placeholder)
|
// Get all clients and send update (placeholder)
|
||||||
clients := w.clients.GetAll()
|
clients := w.clients.GetAll()
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
@ -583,16 +583,16 @@ func (w *World) SendNPCUpdate(npcID int32, updateType string, data map[string]in
|
|||||||
// SendNPCCombatUpdate sends combat-related NPC updates to clients
|
// SendNPCCombatUpdate sends combat-related NPC updates to clients
|
||||||
func (w *World) SendNPCCombatUpdate(npcID int32, targetID int32, combatType string, damage int32) {
|
func (w *World) SendNPCCombatUpdate(npcID int32, targetID int32, combatType string, damage int32) {
|
||||||
// TODO: Implement NPC combat update broadcasting
|
// TODO: Implement NPC combat update broadcasting
|
||||||
|
|
||||||
fmt.Printf("NPC Combat Update: NPC %d -> Target %d, %s for %d damage\n",
|
fmt.Printf("NPC Combat Update: NPC %d -> Target %d, %s for %d damage\n",
|
||||||
npcID, targetID, combatType, damage)
|
npcID, targetID, combatType, damage)
|
||||||
|
|
||||||
// Send to relevant clients (placeholder)
|
// Send to relevant clients (placeholder)
|
||||||
clients := w.clients.GetAll()
|
clients := w.clients.GetAll()
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.CurrentZone != nil && (client.CharacterID == targetID ||
|
if client.CurrentZone != nil && (client.CharacterID == targetID ||
|
||||||
client.CharacterID == npcID) { // TODO: Proper range check
|
client.CharacterID == npcID) { // TODO: Proper range check
|
||||||
client.SendSimpleMessage(fmt.Sprintf("Combat: NPC %d %s target %d for %d damage",
|
client.SendSimpleMessage(fmt.Sprintf("Combat: NPC %d %s target %d for %d damage",
|
||||||
npcID, combatType, targetID, damage))
|
npcID, combatType, targetID, damage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -604,13 +604,13 @@ type WorldDatabaseAdapter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCharacter implements packets.DatabaseInterface
|
// GetCharacter implements packets.DatabaseInterface
|
||||||
func (wda *WorldDatabaseAdapter) GetCharacter(characterID int32) (map[string]interface{}, error) {
|
func (wda *WorldDatabaseAdapter) GetCharacter(characterID int32) (map[string]any, error) {
|
||||||
// TODO: Implement character loading from database
|
// TODO: Implement character loading from database
|
||||||
return nil, fmt.Errorf("character loading not yet implemented")
|
return nil, fmt.Errorf("character loading not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveCharacter implements packets.DatabaseInterface
|
// SaveCharacter implements packets.DatabaseInterface
|
||||||
func (wda *WorldDatabaseAdapter) SaveCharacter(characterID int32, data map[string]interface{}) error {
|
func (wda *WorldDatabaseAdapter) SaveCharacter(characterID int32, data map[string]any) error {
|
||||||
// TODO: Implement character saving to database
|
// TODO: Implement character saving to database
|
||||||
return fmt.Errorf("character saving not yet implemented")
|
return fmt.Errorf("character saving not yet implemented")
|
||||||
}
|
}
|
||||||
@ -654,8 +654,8 @@ func (wca *WorldClientAdapter) IsInZone() bool {
|
|||||||
// SendPacket implements packets.ClientInterface
|
// SendPacket implements packets.ClientInterface
|
||||||
func (wca *WorldClientAdapter) SendPacket(opcode packets.InternalOpcode, data []byte) error {
|
func (wca *WorldClientAdapter) SendPacket(opcode packets.InternalOpcode, data []byte) error {
|
||||||
// TODO: Implement packet sending via UDP connection
|
// TODO: Implement packet sending via UDP connection
|
||||||
fmt.Printf("Sending packet %s to client %s\n",
|
fmt.Printf("Sending packet %s to client %s\n",
|
||||||
packets.GetInternalOpcodeName(opcode),
|
packets.GetInternalOpcodeName(opcode),
|
||||||
wca.client.CharacterName)
|
wca.client.CharacterName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -684,11 +684,11 @@ func (wsa *WorldServerAdapter) GetClientByID(characterID int32) packets.ClientIn
|
|||||||
func (wsa *WorldServerAdapter) GetAllClients() []packets.ClientInterface {
|
func (wsa *WorldServerAdapter) GetAllClients() []packets.ClientInterface {
|
||||||
clients := wsa.world.clients.GetAll()
|
clients := wsa.world.clients.GetAll()
|
||||||
result := make([]packets.ClientInterface, len(clients))
|
result := make([]packets.ClientInterface, len(clients))
|
||||||
|
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
result[i] = &WorldClientAdapter{client: client, world: wsa.world}
|
result[i] = &WorldClientAdapter{client: client, world: wsa.world}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -718,20 +718,20 @@ func (w *World) CreatePacketContext(client *Client) *packets.PacketContext {
|
|||||||
// HandleItemMove handles item movement within player inventory
|
// HandleItemMove handles item movement within player inventory
|
||||||
func (w *World) HandleItemMove(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleItemMove(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent item move packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent item move packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse move data from packet (fromBagID, fromSlot, toBagID, toSlot)
|
// TODO: Parse move data from packet (fromBagID, fromSlot, toBagID, toSlot)
|
||||||
// For now, use placeholder values for testing
|
// For now, use placeholder values for testing
|
||||||
fromBagID := int32(0)
|
fromBagID := int32(0)
|
||||||
fromSlot := int16(0)
|
fromSlot := int16(0)
|
||||||
toBagID := int32(0)
|
toBagID := int32(0)
|
||||||
toSlot := int16(1)
|
toSlot := int16(1)
|
||||||
|
|
||||||
if w.itemMgr != nil {
|
if w.itemMgr != nil {
|
||||||
err := w.itemMgr.MoveItem(uint32(ctx.Client.GetCharacterID()),
|
err := w.itemMgr.MoveItem(uint32(ctx.Client.GetCharacterID()),
|
||||||
fromBagID, fromSlot, toBagID, toSlot)
|
fromBagID, fromSlot, toBagID, toSlot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.SendSimpleMessage(fmt.Sprintf("Item move failed: %v", err))
|
client.SendSimpleMessage(fmt.Sprintf("Item move failed: %v", err))
|
||||||
@ -740,23 +740,23 @@ func (w *World) HandleItemMove(ctx *packets.PacketContext, packet *packets.Packe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemEquip handles item equipping
|
// HandleItemEquip handles item equipping
|
||||||
func (w *World) HandleItemEquip(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleItemEquip(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent item equip packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent item equip packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse equip data from packet (uniqueID, slot)
|
// TODO: Parse equip data from packet (uniqueID, slot)
|
||||||
// For now, use placeholder values for testing
|
// For now, use placeholder values for testing
|
||||||
uniqueID := int32(1001)
|
uniqueID := int32(1001)
|
||||||
slot := int8(0) // Primary hand
|
slot := int8(0) // Primary hand
|
||||||
|
|
||||||
if w.itemMgr != nil {
|
if w.itemMgr != nil {
|
||||||
err := w.itemMgr.EquipItem(uint32(ctx.Client.GetCharacterID()), uniqueID, slot)
|
err := w.itemMgr.EquipItem(uint32(ctx.Client.GetCharacterID()), uniqueID, slot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -766,22 +766,22 @@ func (w *World) HandleItemEquip(ctx *packets.PacketContext, packet *packets.Pack
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemUnequip handles item unequipping
|
// HandleItemUnequip handles item unequipping
|
||||||
func (w *World) HandleItemUnequip(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleItemUnequip(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent item unequip packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent item unequip packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse unequip data from packet (slot)
|
// TODO: Parse unequip data from packet (slot)
|
||||||
// For now, use placeholder values for testing
|
// For now, use placeholder values for testing
|
||||||
slot := int8(0) // Primary hand
|
slot := int8(0) // Primary hand
|
||||||
|
|
||||||
if w.itemMgr != nil {
|
if w.itemMgr != nil {
|
||||||
err := w.itemMgr.UnequipItem(uint32(ctx.Client.GetCharacterID()), slot)
|
err := w.itemMgr.UnequipItem(uint32(ctx.Client.GetCharacterID()), slot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -791,23 +791,23 @@ func (w *World) HandleItemUnequip(ctx *packets.PacketContext, packet *packets.Pa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemPickup handles picking up world drops
|
// HandleItemPickup handles picking up world drops
|
||||||
func (w *World) HandleItemPickup(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleItemPickup(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent item pickup packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent item pickup packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse pickup data from packet (itemUniqueID)
|
// TODO: Parse pickup data from packet (itemUniqueID)
|
||||||
// For now, use placeholder values for testing
|
// For now, use placeholder values for testing
|
||||||
itemUniqueID := int32(5001)
|
itemUniqueID := int32(5001)
|
||||||
zoneID := int32(1) // Current zone
|
zoneID := int32(1) // Current zone
|
||||||
|
|
||||||
if w.itemMgr != nil {
|
if w.itemMgr != nil {
|
||||||
err := w.itemMgr.PickupWorldDrop(uint32(ctx.Client.GetCharacterID()), itemUniqueID, zoneID)
|
err := w.itemMgr.PickupWorldDrop(uint32(ctx.Client.GetCharacterID()), itemUniqueID, zoneID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -818,29 +818,29 @@ func (w *World) HandleItemPickup(ctx *packets.PacketContext, packet *packets.Pac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemDrop handles dropping items to the world
|
// HandleItemDrop handles dropping items to the world
|
||||||
func (w *World) HandleItemDrop(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleItemDrop(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent item drop packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent item drop packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse drop data from packet (uniqueID, quantity, x, y, z)
|
// TODO: Parse drop data from packet (uniqueID, quantity, x, y, z)
|
||||||
// For now, use placeholder values for testing
|
// For now, use placeholder values for testing
|
||||||
itemID := int32(1001)
|
itemID := int32(1001)
|
||||||
quantity := int16(1)
|
quantity := int16(1)
|
||||||
x, y, z := float32(100), float32(100), float32(50)
|
x, y, z := float32(100), float32(100), float32(50)
|
||||||
zoneID := int32(1)
|
zoneID := int32(1)
|
||||||
|
|
||||||
if w.itemMgr != nil {
|
if w.itemMgr != nil {
|
||||||
// First remove from player inventory (would need to look up by uniqueID)
|
// First remove from player inventory (would need to look up by uniqueID)
|
||||||
// TODO: Get uniqueID from packet and remove from player
|
// TODO: Get uniqueID from packet and remove from player
|
||||||
|
|
||||||
// Create world drop
|
// Create world drop
|
||||||
err := w.itemMgr.CreateWorldDrop(itemID, quantity, x, y, z, zoneID)
|
err := w.itemMgr.CreateWorldDrop(itemID, quantity, x, y, z, zoneID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -851,22 +851,22 @@ func (w *World) HandleItemDrop(ctx *packets.PacketContext, packet *packets.Packe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemExamine handles item examination requests
|
// HandleItemExamine handles item examination requests
|
||||||
func (w *World) HandleItemExamine(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleItemExamine(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent item examine packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent item examine packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse examine data from packet (uniqueID or itemID)
|
// TODO: Parse examine data from packet (uniqueID or itemID)
|
||||||
// For now, use placeholder values for testing
|
// For now, use placeholder values for testing
|
||||||
itemID := int32(1001)
|
itemID := int32(1001)
|
||||||
|
|
||||||
if w.itemMgr != nil {
|
if w.itemMgr != nil {
|
||||||
itemTemplate := w.itemMgr.GetItemTemplate(itemID)
|
itemTemplate := w.itemMgr.GetItemTemplate(itemID)
|
||||||
if itemTemplate == nil {
|
if itemTemplate == nil {
|
||||||
@ -877,41 +877,41 @@ func (w *World) HandleItemExamine(ctx *packets.PacketContext, packet *packets.Pa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemUpdate handles item update notifications
|
// HandleItemUpdate handles item update notifications
|
||||||
func (w *World) HandleItemUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
func (w *World) HandleItemUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
|
||||||
fmt.Printf("Client %s sent item update packet\n", ctx.Client.GetCharacterName())
|
fmt.Printf("Client %s sent item update packet\n", ctx.Client.GetCharacterName())
|
||||||
|
|
||||||
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
|
||||||
if client != nil {
|
if client != nil {
|
||||||
client.UpdateActivity()
|
client.UpdateActivity()
|
||||||
|
|
||||||
// TODO: Parse update data from packet
|
// TODO: Parse update data from packet
|
||||||
// This might be triggered when client needs updated item information
|
// This might be triggered when client needs updated item information
|
||||||
|
|
||||||
if w.itemMgr != nil {
|
if w.itemMgr != nil {
|
||||||
// Send updated inventory to client
|
// Send updated inventory to client
|
||||||
w.SendPlayerInventory(client)
|
w.SendPlayerInventory(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendItemDetails sends detailed item information to a client
|
// SendItemDetails sends detailed item information to a client
|
||||||
func (w *World) SendItemDetails(client *Client, item interface{}) {
|
func (w *World) SendItemDetails(client *Client, item any) {
|
||||||
if w.itemMgr == nil {
|
if w.itemMgr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement actual item detail packet building
|
// TODO: Implement actual item detail packet building
|
||||||
// This would include stats, description, level requirements, etc.
|
// This would include stats, description, level requirements, etc.
|
||||||
|
|
||||||
fmt.Printf("Sending item details to %s\n", client.CharacterName)
|
fmt.Printf("Sending item details to %s\n", client.CharacterName)
|
||||||
|
|
||||||
// Placeholder - send basic item info as chat message
|
// Placeholder - send basic item info as chat message
|
||||||
client.SendSimpleMessage("Item Details: [Item information would be displayed here]")
|
client.SendSimpleMessage("Item Details: [Item information would be displayed here]")
|
||||||
}
|
}
|
||||||
@ -921,52 +921,52 @@ func (w *World) SendPlayerInventory(client *Client) {
|
|||||||
if w.itemMgr == nil {
|
if w.itemMgr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
playerID := uint32(client.CharacterID)
|
playerID := uint32(client.CharacterID)
|
||||||
|
|
||||||
// Get player inventory and equipment
|
// Get player inventory and equipment
|
||||||
inventory, err := w.itemMgr.GetPlayerInventory(playerID)
|
inventory, err := w.itemMgr.GetPlayerInventory(playerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to get inventory for player %d: %v\n", playerID, err)
|
fmt.Printf("Failed to get inventory for player %d: %v\n", playerID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
equipment, err := w.itemMgr.GetPlayerEquipment(playerID, 0) // Base equipment
|
equipment, err := w.itemMgr.GetPlayerEquipment(playerID, 0) // Base equipment
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to get equipment for player %d: %v\n", playerID, err)
|
fmt.Printf("Failed to get equipment for player %d: %v\n", playerID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Build and send inventory packet
|
// TODO: Build and send inventory packet
|
||||||
fmt.Printf("Sending inventory to %s: %d inventory items, %d equipped items\n",
|
fmt.Printf("Sending inventory to %s: %d inventory items, %d equipped items\n",
|
||||||
client.CharacterName, inventory.GetNumberOfItems(), equipment.GetNumberOfItems())
|
client.CharacterName, inventory.GetNumberOfItems(), equipment.GetNumberOfItems())
|
||||||
|
|
||||||
// Placeholder - send summary as chat message
|
// Placeholder - send summary as chat message
|
||||||
client.SendSimpleMessage(fmt.Sprintf("Inventory Update: %d items in inventory, %d equipped",
|
client.SendSimpleMessage(fmt.Sprintf("Inventory Update: %d items in inventory, %d equipped",
|
||||||
inventory.GetNumberOfItems(), equipment.GetNumberOfItems()))
|
inventory.GetNumberOfItems(), equipment.GetNumberOfItems()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendItemUpdate sends item update to client
|
// SendItemUpdate sends item update to client
|
||||||
func (w *World) SendItemUpdate(client *Client, updateType string, itemData map[string]interface{}) {
|
func (w *World) SendItemUpdate(client *Client, updateType string, itemData map[string]any) {
|
||||||
if w.itemMgr == nil {
|
if w.itemMgr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Build and send item update packet
|
// TODO: Build and send item update packet
|
||||||
fmt.Printf("Sending item update to %s: %s - %v\n",
|
fmt.Printf("Sending item update to %s: %s - %v\n",
|
||||||
client.CharacterName, updateType, itemData)
|
client.CharacterName, updateType, itemData)
|
||||||
|
|
||||||
// Placeholder - send update as chat message
|
// Placeholder - send update as chat message
|
||||||
client.SendSimpleMessage(fmt.Sprintf("Item Update: %s", updateType))
|
client.SendSimpleMessage(fmt.Sprintf("Item Update: %s", updateType))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastItemUpdate broadcasts item updates to nearby players
|
// BroadcastItemUpdate broadcasts item updates to nearby players
|
||||||
func (w *World) BroadcastItemUpdate(sourcePlayerID uint32, updateType string, itemData map[string]interface{}) {
|
func (w *World) BroadcastItemUpdate(sourcePlayerID uint32, updateType string, itemData map[string]any) {
|
||||||
// TODO: Implement item update broadcasting (for things like equipment changes visible to others)
|
// TODO: Implement item update broadcasting (for things like equipment changes visible to others)
|
||||||
|
|
||||||
fmt.Printf("Broadcasting item update from player %d: %s - %v\n",
|
fmt.Printf("Broadcasting item update from player %d: %s - %v\n",
|
||||||
sourcePlayerID, updateType, itemData)
|
sourcePlayerID, updateType, itemData)
|
||||||
|
|
||||||
// Send to players in range (placeholder)
|
// Send to players in range (placeholder)
|
||||||
clients := w.clients.GetAll()
|
clients := w.clients.GetAll()
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
@ -975,4 +975,4 @@ func (w *World) BroadcastItemUpdate(sourcePlayerID uint32, updateType string, it
|
|||||||
client.SendSimpleMessage(fmt.Sprintf("Player item update: %s", updateType))
|
client.SendSimpleMessage(fmt.Sprintf("Player item update: %s", updateType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,25 +3,25 @@ package world
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"eq2emu/internal/titles"
|
|
||||||
"eq2emu/internal/database"
|
"eq2emu/internal/database"
|
||||||
|
"eq2emu/internal/titles"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TitleManager manages titles for the world server
|
// TitleManager manages titles for the world server
|
||||||
type TitleManager struct {
|
type TitleManager struct {
|
||||||
titleManager *titles.TitleManager
|
titleManager *titles.TitleManager
|
||||||
integrationMgr *titles.IntegrationManager
|
integrationMgr *titles.IntegrationManager
|
||||||
database *database.Database
|
database *database.Database
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTitleManager creates a new title manager for the world server
|
// NewTitleManager creates a new title manager for the world server
|
||||||
func NewTitleManager(db *database.Database) *TitleManager {
|
func NewTitleManager(db *database.Database) *TitleManager {
|
||||||
titleMgr := titles.NewTitleManager()
|
titleMgr := titles.NewTitleManager()
|
||||||
integrationMgr := titles.NewIntegrationManager(titleMgr)
|
integrationMgr := titles.NewIntegrationManager(titleMgr)
|
||||||
|
|
||||||
return &TitleManager{
|
return &TitleManager{
|
||||||
titleManager: titleMgr,
|
titleManager: titleMgr,
|
||||||
integrationMgr: integrationMgr,
|
integrationMgr: integrationMgr,
|
||||||
@ -32,19 +32,19 @@ func NewTitleManager(db *database.Database) *TitleManager {
|
|||||||
// LoadTitles loads all titles from database
|
// LoadTitles loads all titles from database
|
||||||
func (tm *TitleManager) LoadTitles() error {
|
func (tm *TitleManager) LoadTitles() error {
|
||||||
fmt.Println("Loading master title list...")
|
fmt.Println("Loading master title list...")
|
||||||
|
|
||||||
pool := tm.database.GetPool()
|
pool := tm.database.GetPool()
|
||||||
if pool == nil {
|
if pool == nil {
|
||||||
return fmt.Errorf("database pool is nil")
|
return fmt.Errorf("database pool is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement title loading from database when database functions are available
|
// TODO: Implement title loading from database when database functions are available
|
||||||
// For now, create some default titles for testing
|
// For now, create some default titles for testing
|
||||||
err := tm.createDefaultTitles()
|
err := tm.createDefaultTitles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create default titles: %w", err)
|
return fmt.Errorf("failed to create default titles: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Loaded %d titles\n", tm.titleManager.GetMasterList().GetTitleCount())
|
fmt.Printf("Loaded %d titles\n", tm.titleManager.GetMasterList().GetTitleCount())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ func (tm *TitleManager) LoadTitles() error {
|
|||||||
// createDefaultTitles creates some default titles for testing
|
// createDefaultTitles creates some default titles for testing
|
||||||
func (tm *TitleManager) createDefaultTitles() error {
|
func (tm *TitleManager) createDefaultTitles() error {
|
||||||
masterList := tm.titleManager.GetMasterList()
|
masterList := tm.titleManager.GetMasterList()
|
||||||
|
|
||||||
// Achievement-based titles
|
// Achievement-based titles
|
||||||
achievementTitles := map[string]*titles.Title{
|
achievementTitles := map[string]*titles.Title{
|
||||||
"First Blood": {
|
"First Blood": {
|
||||||
@ -136,7 +136,7 @@ func (tm *TitleManager) createDefaultTitles() error {
|
|||||||
Description: "Granted for leveling up",
|
Description: "Granted for leveling up",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add titles to master list
|
// Add titles to master list
|
||||||
for name, title := range achievementTitles {
|
for name, title := range achievementTitles {
|
||||||
err := masterList.AddTitle(title)
|
err := masterList.AddTitle(title)
|
||||||
@ -144,7 +144,7 @@ func (tm *TitleManager) createDefaultTitles() error {
|
|||||||
fmt.Printf("Warning: Failed to add title '%s': %v\n", name, err)
|
fmt.Printf("Warning: Failed to add title '%s': %v\n", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,12 +173,12 @@ func (tm *TitleManager) SetupAchievementIntegration() {
|
|||||||
// Setup callback to handle achievement completions
|
// Setup callback to handle achievement completions
|
||||||
tm.integrationMgr.AddTitleEarnedCallback(func(playerID, titleID int32, source string) {
|
tm.integrationMgr.AddTitleEarnedCallback(func(playerID, titleID int32, source string) {
|
||||||
fmt.Printf("Player %d earned title %d from %s\n", playerID, titleID, source)
|
fmt.Printf("Player %d earned title %d from %s\n", playerID, titleID, source)
|
||||||
|
|
||||||
// TODO: Send title granted packet to client
|
// TODO: Send title granted packet to client
|
||||||
// TODO: Send title list update to client
|
// TODO: Send title list update to client
|
||||||
// TODO: Broadcast title earned message if appropriate
|
// TODO: Broadcast title earned message if appropriate
|
||||||
})
|
})
|
||||||
|
|
||||||
fmt.Println("Achievement-to-title integration setup complete")
|
fmt.Println("Achievement-to-title integration setup complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,41 +189,41 @@ func (tm *TitleManager) ProcessAchievementCompletion(playerID int32, achievement
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to process achievement completion: %w", err)
|
return fmt.Errorf("failed to process achievement completion: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify integration system
|
// Notify integration system
|
||||||
tm.integrationMgr.NotifyTitleEarned(playerID, 0, "achievement") // Title ID is handled internally
|
tm.integrationMgr.NotifyTitleEarned(playerID, 0, "achievement") // Title ID is handled internally
|
||||||
|
|
||||||
fmt.Printf("Processed achievement completion %d for player %d\n", achievementID, playerID)
|
fmt.Printf("Processed achievement completion %d for player %d\n", achievementID, playerID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns title system statistics
|
// GetStatistics returns title system statistics
|
||||||
func (tm *TitleManager) GetStatistics() map[string]interface{} {
|
func (tm *TitleManager) GetStatistics() map[string]any {
|
||||||
tm.mutex.RLock()
|
tm.mutex.RLock()
|
||||||
defer tm.mutex.RUnlock()
|
defer tm.mutex.RUnlock()
|
||||||
|
|
||||||
// Get statistics from the underlying title manager
|
// Get statistics from the underlying title manager
|
||||||
titleManagerStats := tm.titleManager.GetStatistics()
|
titleManagerStats := tm.titleManager.GetStatistics()
|
||||||
|
|
||||||
// Combine with our own statistics
|
// Combine with our own statistics
|
||||||
stats := map[string]interface{}{
|
stats := map[string]any{
|
||||||
"total_titles": tm.titleManager.GetMasterList().GetTitleCount(),
|
"total_titles": tm.titleManager.GetMasterList().GetTitleCount(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add statistics from the title manager
|
// Add statistics from the title manager
|
||||||
for key, value := range titleManagerStats {
|
for key, value := range titleManagerStats {
|
||||||
stats[key] = value
|
stats[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown gracefully shuts down the title manager
|
// Shutdown gracefully shuts down the title manager
|
||||||
func (tm *TitleManager) Shutdown() {
|
func (tm *TitleManager) Shutdown() {
|
||||||
fmt.Println("Shutting down title manager...")
|
fmt.Println("Shutting down title manager...")
|
||||||
|
|
||||||
// TODO: Save player title data to database
|
// TODO: Save player title data to database
|
||||||
// TODO: Cleanup any background processes
|
// TODO: Cleanup any background processes
|
||||||
|
|
||||||
fmt.Println("Title manager shutdown complete")
|
fmt.Println("Title manager shutdown complete")
|
||||||
}
|
}
|
||||||
|
@ -16,88 +16,88 @@ import (
|
|||||||
// World represents the main world server instance
|
// World represents the main world server instance
|
||||||
type World struct {
|
type World struct {
|
||||||
// Core components
|
// Core components
|
||||||
db *database.Database
|
db *database.Database
|
||||||
commandManager *commands.CommandManager
|
commandManager *commands.CommandManager
|
||||||
rulesManager *rules.RuleManager
|
rulesManager *rules.RuleManager
|
||||||
|
|
||||||
// Server configuration
|
// Server configuration
|
||||||
config *WorldConfig
|
config *WorldConfig
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
shutdownTime *time.Time
|
shutdownTime *time.Time
|
||||||
shutdownReason string
|
shutdownReason string
|
||||||
|
|
||||||
// World time management
|
// World time management
|
||||||
worldTime *WorldTime
|
worldTime *WorldTime
|
||||||
worldTimeTicker *time.Ticker
|
worldTimeTicker *time.Ticker
|
||||||
|
|
||||||
// Zones management
|
// Zones management
|
||||||
zones *ZoneList
|
zones *ZoneList
|
||||||
|
|
||||||
// Client management
|
// Client management
|
||||||
clients *ClientList
|
clients *ClientList
|
||||||
|
|
||||||
// Achievement system
|
// Achievement system
|
||||||
achievementMgr *AchievementManager
|
achievementMgr *AchievementManager
|
||||||
|
|
||||||
// Title system
|
// Title system
|
||||||
titleMgr *TitleManager
|
titleMgr *TitleManager
|
||||||
|
|
||||||
// NPC system
|
// NPC system
|
||||||
npcMgr *NPCManager
|
npcMgr *NPCManager
|
||||||
|
|
||||||
// Item system
|
// Item system
|
||||||
itemMgr *ItemManager
|
itemMgr *ItemManager
|
||||||
|
|
||||||
// Master lists (singletons)
|
// Master lists (singletons)
|
||||||
masterSpells interface{} // TODO: implement spell manager
|
masterSpells any // TODO: implement spell manager
|
||||||
masterQuests interface{} // TODO: implement quest manager
|
masterQuests any // TODO: implement quest manager
|
||||||
masterSkills interface{} // TODO: implement skill manager
|
masterSkills any // TODO: implement skill manager
|
||||||
masterFactions interface{} // TODO: implement faction manager
|
masterFactions any // TODO: implement faction manager
|
||||||
|
|
||||||
// Server statistics
|
// Server statistics
|
||||||
stats *ServerStatistics
|
stats *ServerStatistics
|
||||||
|
|
||||||
// Synchronization
|
// Synchronization
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// WorldConfig holds world server configuration
|
// WorldConfig holds world server configuration
|
||||||
type WorldConfig struct {
|
type WorldConfig struct {
|
||||||
// Network settings
|
// Network settings
|
||||||
ListenAddr string `json:"listen_addr"`
|
ListenAddr string `json:"listen_addr"`
|
||||||
ListenPort int `json:"listen_port"`
|
ListenPort int `json:"listen_port"`
|
||||||
MaxClients int `json:"max_clients"`
|
MaxClients int `json:"max_clients"`
|
||||||
|
|
||||||
// Web interface settings
|
// Web interface settings
|
||||||
WebAddr string `json:"web_addr"`
|
WebAddr string `json:"web_addr"`
|
||||||
WebPort int `json:"web_port"`
|
WebPort int `json:"web_port"`
|
||||||
WebCertFile string `json:"web_cert_file"`
|
WebCertFile string `json:"web_cert_file"`
|
||||||
WebKeyFile string `json:"web_key_file"`
|
WebKeyFile string `json:"web_key_file"`
|
||||||
WebKeyPassword string `json:"web_key_password"`
|
WebKeyPassword string `json:"web_key_password"`
|
||||||
|
|
||||||
// Database settings
|
// Database settings
|
||||||
DatabaseType string `json:"database_type"` // "sqlite" or "mysql"
|
DatabaseType string `json:"database_type"` // "sqlite" or "mysql"
|
||||||
DatabasePath string `json:"database_path"` // For SQLite: file path
|
DatabasePath string `json:"database_path"` // For SQLite: file path
|
||||||
DatabaseHost string `json:"database_host"` // For MySQL: hostname
|
DatabaseHost string `json:"database_host"` // For MySQL: hostname
|
||||||
DatabasePort int `json:"database_port"` // For MySQL: port
|
DatabasePort int `json:"database_port"` // For MySQL: port
|
||||||
DatabaseName string `json:"database_name"` // For MySQL: database name
|
DatabaseName string `json:"database_name"` // For MySQL: database name
|
||||||
DatabaseUser string `json:"database_user"` // For MySQL: username
|
DatabaseUser string `json:"database_user"` // For MySQL: username
|
||||||
DatabasePass string `json:"database_pass"` // For MySQL: password
|
DatabasePass string `json:"database_pass"` // For MySQL: password
|
||||||
|
|
||||||
// Server settings
|
// Server settings
|
||||||
ServerName string `json:"server_name"`
|
ServerName string `json:"server_name"`
|
||||||
ServerMOTD string `json:"server_motd"`
|
ServerMOTD string `json:"server_motd"`
|
||||||
LogLevel string `json:"log_level"`
|
LogLevel string `json:"log_level"`
|
||||||
|
|
||||||
// Game settings
|
// Game settings
|
||||||
XPRate float32 `json:"xp_rate"`
|
XPRate float32 `json:"xp_rate"`
|
||||||
TSXPRate float32 `json:"ts_xp_rate"`
|
TSXPRate float32 `json:"ts_xp_rate"`
|
||||||
CoinRate float32 `json:"coin_rate"`
|
CoinRate float32 `json:"coin_rate"`
|
||||||
LootRate float32 `json:"loot_rate"`
|
LootRate float32 `json:"loot_rate"`
|
||||||
|
|
||||||
// Login server settings
|
// Login server settings
|
||||||
LoginServerAddr string `json:"login_server_addr"`
|
LoginServerAddr string `json:"login_server_addr"`
|
||||||
LoginServerPort int `json:"login_server_port"`
|
LoginServerPort int `json:"login_server_port"`
|
||||||
@ -117,29 +117,29 @@ type WorldTime struct {
|
|||||||
// ServerStatistics tracks server metrics
|
// ServerStatistics tracks server metrics
|
||||||
type ServerStatistics struct {
|
type ServerStatistics struct {
|
||||||
// Server info
|
// Server info
|
||||||
ServerCreated time.Time
|
ServerCreated time.Time
|
||||||
ServerStartTime time.Time
|
ServerStartTime time.Time
|
||||||
|
|
||||||
// Connection stats
|
// Connection stats
|
||||||
TotalConnections int64
|
TotalConnections int64
|
||||||
CurrentConnections int32
|
CurrentConnections int32
|
||||||
MaxConnections int32
|
MaxConnections int32
|
||||||
|
|
||||||
// Character stats
|
// Character stats
|
||||||
TotalAccounts int32
|
TotalAccounts int32
|
||||||
TotalCharacters int32
|
TotalCharacters int32
|
||||||
AverageCharLevel float32
|
AverageCharLevel float32
|
||||||
|
|
||||||
// Zone stats
|
// Zone stats
|
||||||
ActiveZones int32
|
ActiveZones int32
|
||||||
ActiveInstances int32
|
ActiveInstances int32
|
||||||
|
|
||||||
// Performance stats
|
// Performance stats
|
||||||
CPUUsage float32
|
CPUUsage float32
|
||||||
MemoryUsage int64
|
MemoryUsage int64
|
||||||
PeakMemoryUsage int64
|
PeakMemoryUsage int64
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWorld creates a new world server instance
|
// NewWorld creates a new world server instance
|
||||||
@ -147,7 +147,7 @@ func NewWorld(config *WorldConfig) (*World, error) {
|
|||||||
// Initialize database
|
// Initialize database
|
||||||
var db *database.Database
|
var db *database.Database
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch strings.ToLower(config.DatabaseType) {
|
switch strings.ToLower(config.DatabaseType) {
|
||||||
case "mysql", "mariadb":
|
case "mysql", "mariadb":
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci",
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci",
|
||||||
@ -163,35 +163,35 @@ func NewWorld(config *WorldConfig) (*World, error) {
|
|||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported database type: %s", config.DatabaseType)
|
return nil, fmt.Errorf("unsupported database type: %s", config.DatabaseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize database: %w", err)
|
return nil, fmt.Errorf("failed to initialize database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize command manager
|
// Initialize command manager
|
||||||
cmdManager, err := commands.InitializeCommands()
|
cmdManager, err := commands.InitializeCommands()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize commands: %w", err)
|
return nil, fmt.Errorf("failed to initialize commands: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize rules manager
|
// Initialize rules manager
|
||||||
rulesManager := rules.NewRuleManager()
|
rulesManager := rules.NewRuleManager()
|
||||||
|
|
||||||
// Initialize achievement manager
|
// Initialize achievement manager
|
||||||
achievementMgr := NewAchievementManager(db)
|
achievementMgr := NewAchievementManager(db)
|
||||||
|
|
||||||
// Initialize title manager
|
// Initialize title manager
|
||||||
titleMgr := NewTitleManager(db)
|
titleMgr := NewTitleManager(db)
|
||||||
|
|
||||||
// Initialize NPC manager
|
// Initialize NPC manager
|
||||||
npcMgr := NewNPCManager(db)
|
npcMgr := NewNPCManager(db)
|
||||||
|
|
||||||
// Initialize item manager
|
// Initialize item manager
|
||||||
itemMgr := NewItemManager(db)
|
itemMgr := NewItemManager(db)
|
||||||
|
|
||||||
// Create context
|
// Create context
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
w := &World{
|
w := &World{
|
||||||
db: db,
|
db: db,
|
||||||
commandManager: cmdManager,
|
commandManager: cmdManager,
|
||||||
@ -205,24 +205,24 @@ func NewWorld(config *WorldConfig) (*World, error) {
|
|||||||
worldTime: &WorldTime{Year: 3721, Month: 1, Day: 1, Hour: 12, Minute: 0},
|
worldTime: &WorldTime{Year: 3721, Month: 1, Day: 1, Hour: 12, Minute: 0},
|
||||||
zones: NewZoneList(),
|
zones: NewZoneList(),
|
||||||
clients: NewClientList(),
|
clients: NewClientList(),
|
||||||
stats: &ServerStatistics{
|
stats: &ServerStatistics{
|
||||||
ServerStartTime: time.Now(),
|
ServerStartTime: time.Now(),
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set world references for cross-system communication
|
// Set world references for cross-system communication
|
||||||
achievementMgr.SetWorld(w)
|
achievementMgr.SetWorld(w)
|
||||||
npcMgr.SetWorld(w)
|
npcMgr.SetWorld(w)
|
||||||
itemMgr.SetWorld(w)
|
itemMgr.SetWorld(w)
|
||||||
|
|
||||||
// Load server data from database
|
// Load server data from database
|
||||||
if err := w.loadServerData(); err != nil {
|
if err := w.loadServerData(); err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, fmt.Errorf("failed to load server data: %w", err)
|
return nil, fmt.Errorf("failed to load server data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,33 +230,33 @@ func NewWorld(config *WorldConfig) (*World, error) {
|
|||||||
func (w *World) Start() error {
|
func (w *World) Start() error {
|
||||||
w.mutex.Lock()
|
w.mutex.Lock()
|
||||||
defer w.mutex.Unlock()
|
defer w.mutex.Unlock()
|
||||||
|
|
||||||
fmt.Printf("Starting EQ2Go World Server '%s'...\n", w.config.ServerName)
|
fmt.Printf("Starting EQ2Go World Server '%s'...\n", w.config.ServerName)
|
||||||
fmt.Printf("Listen Address: %s:%d\n", w.config.ListenAddr, w.config.ListenPort)
|
fmt.Printf("Listen Address: %s:%d\n", w.config.ListenAddr, w.config.ListenPort)
|
||||||
|
|
||||||
// Register packet handlers
|
// Register packet handlers
|
||||||
w.RegisterPacketHandlers()
|
w.RegisterPacketHandlers()
|
||||||
|
|
||||||
// Load sample opcode mappings (TODO: Load from configuration files)
|
// Load sample opcode mappings (TODO: Load from configuration files)
|
||||||
w.loadSampleOpcodeMappings()
|
w.loadSampleOpcodeMappings()
|
||||||
|
|
||||||
// Start world time ticker
|
// Start world time ticker
|
||||||
w.worldTimeTicker = time.NewTicker(3 * time.Second) // EQ2 time tick
|
w.worldTimeTicker = time.NewTicker(3 * time.Second) // EQ2 time tick
|
||||||
w.wg.Add(1)
|
w.wg.Add(1)
|
||||||
go w.worldTimeTick()
|
go w.worldTimeTick()
|
||||||
|
|
||||||
// Start statistics updater
|
// Start statistics updater
|
||||||
w.wg.Add(1)
|
w.wg.Add(1)
|
||||||
go w.updateStatistics()
|
go w.updateStatistics()
|
||||||
|
|
||||||
// Start zone watchdog
|
// Start zone watchdog
|
||||||
w.wg.Add(1)
|
w.wg.Add(1)
|
||||||
go w.zoneWatchdog()
|
go w.zoneWatchdog()
|
||||||
|
|
||||||
// Start client handler
|
// Start client handler
|
||||||
w.wg.Add(1)
|
w.wg.Add(1)
|
||||||
go w.clientHandler()
|
go w.clientHandler()
|
||||||
|
|
||||||
fmt.Println("World server started successfully!")
|
fmt.Println("World server started successfully!")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -265,46 +265,46 @@ func (w *World) Start() error {
|
|||||||
func (w *World) Stop() error {
|
func (w *World) Stop() error {
|
||||||
w.mutex.Lock()
|
w.mutex.Lock()
|
||||||
defer w.mutex.Unlock()
|
defer w.mutex.Unlock()
|
||||||
|
|
||||||
fmt.Println("Shutting down world server...")
|
fmt.Println("Shutting down world server...")
|
||||||
|
|
||||||
// Cancel context to signal shutdown
|
// Cancel context to signal shutdown
|
||||||
w.cancel()
|
w.cancel()
|
||||||
|
|
||||||
// Stop world time ticker
|
// Stop world time ticker
|
||||||
if w.worldTimeTicker != nil {
|
if w.worldTimeTicker != nil {
|
||||||
w.worldTimeTicker.Stop()
|
w.worldTimeTicker.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect all clients
|
// Disconnect all clients
|
||||||
w.clients.DisconnectAll("Server shutting down")
|
w.clients.DisconnectAll("Server shutting down")
|
||||||
|
|
||||||
// Shutdown all zones
|
// Shutdown all zones
|
||||||
w.zones.ShutdownAll()
|
w.zones.ShutdownAll()
|
||||||
|
|
||||||
// Wait for all goroutines to finish
|
// Wait for all goroutines to finish
|
||||||
w.wg.Wait()
|
w.wg.Wait()
|
||||||
|
|
||||||
// Shutdown achievement manager
|
// Shutdown achievement manager
|
||||||
if w.achievementMgr != nil {
|
if w.achievementMgr != nil {
|
||||||
w.achievementMgr.Shutdown()
|
w.achievementMgr.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown title manager
|
// Shutdown title manager
|
||||||
if w.titleMgr != nil {
|
if w.titleMgr != nil {
|
||||||
w.titleMgr.Shutdown()
|
w.titleMgr.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown NPC manager
|
// Shutdown NPC manager
|
||||||
if w.npcMgr != nil {
|
if w.npcMgr != nil {
|
||||||
w.npcMgr.Shutdown()
|
w.npcMgr.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close database
|
// Close database
|
||||||
if w.db != nil {
|
if w.db != nil {
|
||||||
w.db.Close()
|
w.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("World server shutdown complete.")
|
fmt.Println("World server shutdown complete.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -313,7 +313,7 @@ func (w *World) Stop() error {
|
|||||||
func (w *World) Process() {
|
func (w *World) Process() {
|
||||||
ticker := time.NewTicker(100 * time.Millisecond)
|
ticker := time.NewTicker(100 * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-w.ctx.Done():
|
case <-w.ctx.Done():
|
||||||
@ -328,16 +328,16 @@ func (w *World) Process() {
|
|||||||
func (w *World) processFrame() {
|
func (w *World) processFrame() {
|
||||||
// Process zones
|
// Process zones
|
||||||
w.zones.ProcessAll()
|
w.zones.ProcessAll()
|
||||||
|
|
||||||
// Process clients
|
// Process clients
|
||||||
w.clients.ProcessAll()
|
w.clients.ProcessAll()
|
||||||
|
|
||||||
// Process NPCs
|
// Process NPCs
|
||||||
w.npcMgr.ProcessNPCs()
|
w.npcMgr.ProcessNPCs()
|
||||||
|
|
||||||
// Check for scheduled shutdown
|
// Check for scheduled shutdown
|
||||||
w.checkShutdown()
|
w.checkShutdown()
|
||||||
|
|
||||||
// Update vitality
|
// Update vitality
|
||||||
w.updateVitality()
|
w.updateVitality()
|
||||||
}
|
}
|
||||||
@ -345,14 +345,14 @@ func (w *World) processFrame() {
|
|||||||
// worldTimeTick advances the in-game time
|
// worldTimeTick advances the in-game time
|
||||||
func (w *World) worldTimeTick() {
|
func (w *World) worldTimeTick() {
|
||||||
defer w.wg.Done()
|
defer w.wg.Done()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-w.ctx.Done():
|
case <-w.ctx.Done():
|
||||||
return
|
return
|
||||||
case <-w.worldTimeTicker.C:
|
case <-w.worldTimeTicker.C:
|
||||||
w.worldTime.mutex.Lock()
|
w.worldTime.mutex.Lock()
|
||||||
|
|
||||||
// Advance time (3 seconds = 1 game minute)
|
// Advance time (3 seconds = 1 game minute)
|
||||||
w.worldTime.Minute++
|
w.worldTime.Minute++
|
||||||
if w.worldTime.Minute >= 60 {
|
if w.worldTime.Minute >= 60 {
|
||||||
@ -371,9 +371,9 @@ func (w *World) worldTimeTick() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.worldTime.mutex.Unlock()
|
w.worldTime.mutex.Unlock()
|
||||||
|
|
||||||
// Send time update to all zones
|
// Send time update to all zones
|
||||||
w.zones.SendTimeUpdate(w.worldTime)
|
w.zones.SendTimeUpdate(w.worldTime)
|
||||||
}
|
}
|
||||||
@ -383,24 +383,24 @@ func (w *World) worldTimeTick() {
|
|||||||
// updateStatistics updates server statistics periodically
|
// updateStatistics updates server statistics periodically
|
||||||
func (w *World) updateStatistics() {
|
func (w *World) updateStatistics() {
|
||||||
defer w.wg.Done()
|
defer w.wg.Done()
|
||||||
|
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-w.ctx.Done():
|
case <-w.ctx.Done():
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
w.stats.mutex.Lock()
|
w.stats.mutex.Lock()
|
||||||
|
|
||||||
// Update current stats
|
// Update current stats
|
||||||
w.stats.CurrentConnections = w.clients.Count()
|
w.stats.CurrentConnections = w.clients.Count()
|
||||||
w.stats.ActiveZones = w.zones.Count()
|
w.stats.ActiveZones = w.zones.Count()
|
||||||
w.stats.ActiveInstances = w.zones.CountInstances()
|
w.stats.ActiveInstances = w.zones.CountInstances()
|
||||||
|
|
||||||
// TODO: Update other statistics
|
// TODO: Update other statistics
|
||||||
|
|
||||||
w.stats.mutex.Unlock()
|
w.stats.mutex.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -409,10 +409,10 @@ func (w *World) updateStatistics() {
|
|||||||
// zoneWatchdog monitors zone health
|
// zoneWatchdog monitors zone health
|
||||||
func (w *World) zoneWatchdog() {
|
func (w *World) zoneWatchdog() {
|
||||||
defer w.wg.Done()
|
defer w.wg.Done()
|
||||||
|
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-w.ctx.Done():
|
case <-w.ctx.Done():
|
||||||
@ -420,7 +420,7 @@ func (w *World) zoneWatchdog() {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
// Check zone health
|
// Check zone health
|
||||||
w.zones.CheckHealth()
|
w.zones.CheckHealth()
|
||||||
|
|
||||||
// Clean up dead zones
|
// Clean up dead zones
|
||||||
w.zones.CleanupDead()
|
w.zones.CleanupDead()
|
||||||
}
|
}
|
||||||
@ -430,18 +430,18 @@ func (w *World) zoneWatchdog() {
|
|||||||
// clientHandler handles incoming client connections
|
// clientHandler handles incoming client connections
|
||||||
func (w *World) clientHandler() {
|
func (w *World) clientHandler() {
|
||||||
defer w.wg.Done()
|
defer w.wg.Done()
|
||||||
|
|
||||||
// TODO: Implement UDP listener and client connection handling
|
// TODO: Implement UDP listener and client connection handling
|
||||||
// This will create a UDP server that listens for incoming connections
|
// This will create a UDP server that listens for incoming connections
|
||||||
// and creates Client instances for each connection
|
// and creates Client instances for each connection
|
||||||
|
|
||||||
fmt.Printf("Client handler ready - waiting for UDP server integration\n")
|
fmt.Printf("Client handler ready - waiting for UDP server integration\n")
|
||||||
fmt.Printf("When UDP integration is complete, this will:\n")
|
fmt.Printf("When UDP integration is complete, this will:\n")
|
||||||
fmt.Printf(" - Listen on %s:%d for client connections\n", w.config.ListenAddr, w.config.ListenPort)
|
fmt.Printf(" - Listen on %s:%d for client connections\n", w.config.ListenAddr, w.config.ListenPort)
|
||||||
fmt.Printf(" - Create Client instances for new connections\n")
|
fmt.Printf(" - Create Client instances for new connections\n")
|
||||||
fmt.Printf(" - Process incoming packets through the opcode system\n")
|
fmt.Printf(" - Process incoming packets through the opcode system\n")
|
||||||
fmt.Printf(" - Handle client authentication and zone entry\n")
|
fmt.Printf(" - Handle client authentication and zone entry\n")
|
||||||
|
|
||||||
// For now, just wait for shutdown
|
// For now, just wait for shutdown
|
||||||
<-w.ctx.Done()
|
<-w.ctx.Done()
|
||||||
}
|
}
|
||||||
@ -449,39 +449,39 @@ func (w *World) clientHandler() {
|
|||||||
// loadServerData loads initial data from database
|
// loadServerData loads initial data from database
|
||||||
func (w *World) loadServerData() error {
|
func (w *World) loadServerData() error {
|
||||||
fmt.Println("Loading server data from database...")
|
fmt.Println("Loading server data from database...")
|
||||||
|
|
||||||
// Load achievements
|
// Load achievements
|
||||||
if err := w.achievementMgr.LoadAchievements(); err != nil {
|
if err := w.achievementMgr.LoadAchievements(); err != nil {
|
||||||
fmt.Printf("Warning: Failed to load achievements: %v\n", err)
|
fmt.Printf("Warning: Failed to load achievements: %v\n", err)
|
||||||
// Don't fail startup if achievements don't load - server can still run
|
// Don't fail startup if achievements don't load - server can still run
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load titles
|
// Load titles
|
||||||
if err := w.titleMgr.LoadTitles(); err != nil {
|
if err := w.titleMgr.LoadTitles(); err != nil {
|
||||||
fmt.Printf("Warning: Failed to load titles: %v\n", err)
|
fmt.Printf("Warning: Failed to load titles: %v\n", err)
|
||||||
// Don't fail startup if titles don't load - server can still run
|
// Don't fail startup if titles don't load - server can still run
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load NPCs
|
// Load NPCs
|
||||||
if err := w.npcMgr.LoadNPCs(); err != nil {
|
if err := w.npcMgr.LoadNPCs(); err != nil {
|
||||||
fmt.Printf("Warning: Failed to load NPCs: %v\n", err)
|
fmt.Printf("Warning: Failed to load NPCs: %v\n", err)
|
||||||
// Don't fail startup if NPCs don't load - server can still run
|
// Don't fail startup if NPCs don't load - server can still run
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load items
|
// Load items
|
||||||
if err := w.itemMgr.LoadItems(); err != nil {
|
if err := w.itemMgr.LoadItems(); err != nil {
|
||||||
fmt.Printf("Warning: Failed to load items: %v\n", err)
|
fmt.Printf("Warning: Failed to load items: %v\n", err)
|
||||||
// Don't fail startup if items don't load - server can still run
|
// Don't fail startup if items don't load - server can still run
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup title and achievement integration
|
// Setup title and achievement integration
|
||||||
w.setupTitleAchievementIntegration()
|
w.setupTitleAchievementIntegration()
|
||||||
|
|
||||||
// Load rules (TODO: implement when rules database integration is ready)
|
// Load rules (TODO: implement when rules database integration is ready)
|
||||||
// if err := w.rulesManager.LoadRules(); err != nil {
|
// if err := w.rulesManager.LoadRules(); err != nil {
|
||||||
// return fmt.Errorf("failed to load rules: %w", err)
|
// return fmt.Errorf("failed to load rules: %w", err)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// TODO: Load other server data
|
// TODO: Load other server data
|
||||||
// - Master spell list
|
// - Master spell list
|
||||||
// - Master item list
|
// - Master item list
|
||||||
@ -491,7 +491,7 @@ func (w *World) loadServerData() error {
|
|||||||
// - Starting skills/spells
|
// - Starting skills/spells
|
||||||
// - Merchant data
|
// - Merchant data
|
||||||
// - Transport data
|
// - Transport data
|
||||||
|
|
||||||
fmt.Println("Server data loaded successfully.")
|
fmt.Println("Server data loaded successfully.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -501,7 +501,7 @@ func (w *World) checkShutdown() {
|
|||||||
w.mutex.RLock()
|
w.mutex.RLock()
|
||||||
shutdownTime := w.shutdownTime
|
shutdownTime := w.shutdownTime
|
||||||
w.mutex.RUnlock()
|
w.mutex.RUnlock()
|
||||||
|
|
||||||
if shutdownTime != nil && time.Now().After(*shutdownTime) {
|
if shutdownTime != nil && time.Now().After(*shutdownTime) {
|
||||||
fmt.Printf("Scheduled shutdown: %s\n", w.shutdownReason)
|
fmt.Printf("Scheduled shutdown: %s\n", w.shutdownReason)
|
||||||
go w.Stop()
|
go w.Stop()
|
||||||
@ -517,11 +517,11 @@ func (w *World) updateVitality() {
|
|||||||
func (w *World) ScheduleShutdown(minutes int, reason string) {
|
func (w *World) ScheduleShutdown(minutes int, reason string) {
|
||||||
w.mutex.Lock()
|
w.mutex.Lock()
|
||||||
defer w.mutex.Unlock()
|
defer w.mutex.Unlock()
|
||||||
|
|
||||||
shutdownTime := time.Now().Add(time.Duration(minutes) * time.Minute)
|
shutdownTime := time.Now().Add(time.Duration(minutes) * time.Minute)
|
||||||
w.shutdownTime = &shutdownTime
|
w.shutdownTime = &shutdownTime
|
||||||
w.shutdownReason = reason
|
w.shutdownReason = reason
|
||||||
|
|
||||||
// Announce to all clients
|
// Announce to all clients
|
||||||
message := fmt.Sprintf("Server shutdown scheduled in %d minutes: %s", minutes, reason)
|
message := fmt.Sprintf("Server shutdown scheduled in %d minutes: %s", minutes, reason)
|
||||||
w.clients.BroadcastMessage(message)
|
w.clients.BroadcastMessage(message)
|
||||||
@ -531,11 +531,11 @@ func (w *World) ScheduleShutdown(minutes int, reason string) {
|
|||||||
func (w *World) CancelShutdown() {
|
func (w *World) CancelShutdown() {
|
||||||
w.mutex.Lock()
|
w.mutex.Lock()
|
||||||
defer w.mutex.Unlock()
|
defer w.mutex.Unlock()
|
||||||
|
|
||||||
if w.shutdownTime != nil {
|
if w.shutdownTime != nil {
|
||||||
w.shutdownTime = nil
|
w.shutdownTime = nil
|
||||||
w.shutdownReason = ""
|
w.shutdownReason = ""
|
||||||
|
|
||||||
// Announce cancellation
|
// Announce cancellation
|
||||||
w.clients.BroadcastMessage("Scheduled shutdown has been cancelled.")
|
w.clients.BroadcastMessage("Scheduled shutdown has been cancelled.")
|
||||||
}
|
}
|
||||||
@ -545,7 +545,7 @@ func (w *World) CancelShutdown() {
|
|||||||
func (w *World) GetWorldTime() WorldTime {
|
func (w *World) GetWorldTime() WorldTime {
|
||||||
w.worldTime.mutex.RLock()
|
w.worldTime.mutex.RLock()
|
||||||
defer w.worldTime.mutex.RUnlock()
|
defer w.worldTime.mutex.RUnlock()
|
||||||
|
|
||||||
return WorldTime{
|
return WorldTime{
|
||||||
Year: w.worldTime.Year,
|
Year: w.worldTime.Year,
|
||||||
Month: w.worldTime.Month,
|
Month: w.worldTime.Month,
|
||||||
@ -593,7 +593,7 @@ func (w *World) GetNPCManager() *NPCManager {
|
|||||||
// loadSampleOpcodeMappings loads sample opcode mappings for testing
|
// loadSampleOpcodeMappings loads sample opcode mappings for testing
|
||||||
func (w *World) loadSampleOpcodeMappings() {
|
func (w *World) loadSampleOpcodeMappings() {
|
||||||
fmt.Println("Loading sample opcode mappings...")
|
fmt.Println("Loading sample opcode mappings...")
|
||||||
|
|
||||||
// Sample opcode mappings for a common client version (60013)
|
// Sample opcode mappings for a common client version (60013)
|
||||||
// These should eventually be loaded from configuration files
|
// These should eventually be loaded from configuration files
|
||||||
sampleOpcodes := map[string]uint16{
|
sampleOpcodes := map[string]uint16{
|
||||||
@ -659,7 +659,7 @@ func (w *World) loadSampleOpcodeMappings() {
|
|||||||
"OP_EqSetControlGhostCmd": 0x1006,
|
"OP_EqSetControlGhostCmd": 0x1006,
|
||||||
"OP_EqSetPOVGhostCmd": 0x1007,
|
"OP_EqSetPOVGhostCmd": 0x1007,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load opcodes for client version 60013
|
// Load opcodes for client version 60013
|
||||||
err := packets.LoadGlobalOpcodeMappings(60013, sampleOpcodes)
|
err := packets.LoadGlobalOpcodeMappings(60013, sampleOpcodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -667,7 +667,7 @@ func (w *World) loadSampleOpcodeMappings() {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Printf("Loaded %d opcode mappings for client version 60013\n", len(sampleOpcodes))
|
fmt.Printf("Loaded %d opcode mappings for client version 60013\n", len(sampleOpcodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Load additional client versions and their opcode mappings
|
// TODO: Load additional client versions and their opcode mappings
|
||||||
// This would typically be done from external configuration files
|
// This would typically be done from external configuration files
|
||||||
}
|
}
|
||||||
@ -675,14 +675,14 @@ func (w *World) loadSampleOpcodeMappings() {
|
|||||||
// setupTitleAchievementIntegration sets up integration between titles and achievements
|
// setupTitleAchievementIntegration sets up integration between titles and achievements
|
||||||
func (w *World) setupTitleAchievementIntegration() {
|
func (w *World) setupTitleAchievementIntegration() {
|
||||||
fmt.Println("Setting up title and achievement integration...")
|
fmt.Println("Setting up title and achievement integration...")
|
||||||
|
|
||||||
if w.titleMgr == nil || w.achievementMgr == nil {
|
if w.titleMgr == nil || w.achievementMgr == nil {
|
||||||
fmt.Println("Warning: Cannot setup integration - title or achievement manager is nil")
|
fmt.Println("Warning: Cannot setup integration - title or achievement manager is nil")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup title manager's achievement integration
|
// Setup title manager's achievement integration
|
||||||
w.titleMgr.SetupAchievementIntegration()
|
w.titleMgr.SetupAchievementIntegration()
|
||||||
|
|
||||||
fmt.Println("Title and achievement integration setup complete")
|
fmt.Println("Title and achievement integration setup complete")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user