eq2go/internal/world/packet_handlers.go

705 lines
24 KiB
Go

package world
import (
"eq2emu/internal/packets"
"fmt"
)
// RegisterPacketHandlers registers all world server packet handlers
func (w *World) RegisterPacketHandlers() {
fmt.Println("Registering world server packet handlers...")
// Basic connection and loading handlers
packets.RegisterGlobalHandler(packets.OP_DoneLoadingZoneResourcesMsg, w.HandleDoneLoadingZoneResources)
packets.RegisterGlobalHandler(packets.OP_DoneSendingInitialEntitiesMsg, w.HandleDoneSendingInitialEntities)
packets.RegisterGlobalHandler(packets.OP_DoneLoadingEntityResourcesMsg, w.HandleDoneLoadingEntityResources)
packets.RegisterGlobalHandler(packets.OP_DoneLoadingUIResourcesMsg, w.HandleDoneLoadingUIResources)
// Zone readiness
packets.RegisterGlobalHandler(packets.OP_ReadyToZoneMsg, w.HandleReadyToZone)
// Command handling
packets.RegisterGlobalHandler(packets.OP_ClientCmdMsg, w.HandleClientCommand)
packets.RegisterGlobalHandler(packets.OP_DispatchClientCmdMsg, w.HandleDispatchClientCommand)
// Position updates
packets.RegisterGlobalHandler(packets.OP_UpdatePositionMsg, w.HandlePositionUpdate)
// Chat system
packets.RegisterGlobalHandler(packets.OP_ChatTellChannelMsg, w.HandleChatTellChannel)
packets.RegisterGlobalHandler(packets.OP_ChatTellUserMsg, w.HandleChatTellUser)
// Zone transitions
packets.RegisterGlobalHandler(packets.OP_ChangeZoneMsg, w.HandleChangeZone)
packets.RegisterGlobalHandler(packets.OP_ClientTeleportRequestMsg, w.HandleClientTeleportRequest)
// Achievement system
packets.RegisterGlobalHandler(packets.OP_AchievementUpdateMsg, w.HandleAchievementUpdate)
packets.RegisterGlobalHandler(packets.OP_CharacterAchievements, w.HandleCharacterAchievements)
// Title system
packets.RegisterGlobalHandler(packets.OP_TitleUpdateMsg, w.HandleTitleUpdate)
packets.RegisterGlobalHandler(packets.OP_CharacterTitles, w.HandleCharacterTitles)
packets.RegisterGlobalHandler(packets.OP_SetActiveTitleMsg, w.HandleSetActiveTitle)
// NPC system
packets.RegisterGlobalHandler(packets.OP_NPCAttackMsg, w.HandleNPCAttack)
packets.RegisterGlobalHandler(packets.OP_NPCTargetMsg, w.HandleNPCTarget)
packets.RegisterGlobalHandler(packets.OP_NPCInfoMsg, w.HandleNPCInfo)
packets.RegisterGlobalHandler(packets.OP_NPCSpellCastMsg, w.HandleNPCSpellCast)
packets.RegisterGlobalHandler(packets.OP_NPCMovementMsg, w.HandleNPCMovement)
fmt.Printf("Registered %d packet handlers\n", 21)
}
// HandleDoneLoadingZoneResources handles when client finishes loading zone resources
func (w *World) HandleDoneLoadingZoneResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s finished loading zone resources\n", ctx.Client.GetCharacterName())
// Update client state
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Send initial zone data, spawns, etc.
}
return nil
}
// HandleDoneSendingInitialEntities handles when client finishes receiving initial entities
func (w *World) HandleDoneSendingInitialEntities(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s finished receiving initial entities\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Mark client as fully loaded
}
return nil
}
// HandleDoneLoadingEntityResources handles when client finishes loading entity resources
func (w *World) HandleDoneLoadingEntityResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s finished loading entity resources\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
}
return nil
}
// HandleDoneLoadingUIResources handles when client finishes loading UI resources
func (w *World) HandleDoneLoadingUIResources(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s finished loading UI resources\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Send initial UI packets (character sheet, spellbook, etc.)
}
return nil
}
// HandleReadyToZone handles when client is ready to enter the zone
func (w *World) HandleReadyToZone(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s is ready to enter zone\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Complete zone entry process
// - Send world time
// - Send MOTD
// - Send initial game state
// - Add player to zone
}
return nil
}
// HandleClientCommand handles client command messages
func (w *World) HandleClientCommand(ctx *packets.PacketContext, packet *packets.PacketData) error {
// TODO: Parse command from packet data
// For now, just log the attempt
fmt.Printf("Client %s sent command (raw packet)\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Extract command text and dispatch to command manager
// This will require parsing the packet structure
}
return nil
}
// HandleDispatchClientCommand handles dispatched client commands
func (w *World) HandleDispatchClientCommand(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent dispatched command\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Handle dispatched commands
}
return nil
}
// HandlePositionUpdate handles player position updates
func (w *World) HandlePositionUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
// Position updates are frequent, so only log occasionally
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse position data from packet
// TODO: Update player position in zone
// TODO: Send position update to other players in range
}
return nil
}
// HandleChatTellChannel handles channel chat messages
func (w *World) HandleChatTellChannel(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent channel chat message\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse chat message from packet
// TODO: Validate channel permissions
// TODO: Broadcast message to appropriate recipients
}
return nil
}
// HandleChatTellUser handles direct tell messages
func (w *World) HandleChatTellUser(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent tell message\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse tell message and target from packet
// TODO: Find target player
// TODO: Send message to target
}
return nil
}
// HandleChangeZone handles zone change requests
func (w *World) HandleChangeZone(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s requested zone change\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse zone change request from packet
// TODO: Validate zone change is allowed
// TODO: Begin zone transfer process
}
return nil
}
// HandleClientTeleportRequest handles client teleport requests
func (w *World) HandleClientTeleportRequest(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s requested teleport\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse teleport request from packet
// TODO: Validate teleport permissions
// TODO: Execute teleport
}
return nil
}
// HandleAchievementUpdate handles achievement update requests from client
func (w *World) HandleAchievementUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s requested achievement update\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// Send current achievement data to client
w.SendAchievementData(client)
}
return nil
}
// HandleCharacterAchievements handles character achievements request from client
func (w *World) HandleCharacterAchievements(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s requested character achievements\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// Send complete achievement list to client
w.SendCharacterAchievements(client)
}
return nil
}
// SendAchievementData sends achievement data to a client
func (w *World) SendAchievementData(client *Client) {
if w.achievementMgr == nil {
return
}
characterID := client.CharacterID
// Get player's completed achievements
completedAchievements := w.achievementMgr.GetPlayerCompletedAchievements(characterID)
inProgressAchievements := w.achievementMgr.GetPlayerInProgressAchievements(characterID)
fmt.Printf("Sending achievement data to %s: %d completed, %d in progress\n",
client.CharacterName, len(completedAchievements), len(inProgressAchievements))
// Create achievement update packet
// This would normally build a proper packet structure
totalPoints := w.calculateAchievementPoints(characterID)
// Send packet to client (placeholder - would use actual packet building)
client.SendSimpleMessage(fmt.Sprintf("Achievement Update: %d completed, %d in progress, %d points",
len(completedAchievements), len(inProgressAchievements), totalPoints))
}
// SendCharacterAchievements sends complete character achievements to client
func (w *World) SendCharacterAchievements(client *Client) {
if w.achievementMgr == nil {
return
}
characterID := client.CharacterID
// Get all achievements with player progress
allAchievements := w.achievementMgr.masterList.GetAllAchievements()
characterData := make(map[string]interface{})
for achievementID, achievement := range allAchievements {
progress := w.achievementMgr.GetPlayerProgress(characterID, achievementID)
completed := w.achievementMgr.IsPlayerCompleted(characterID, achievementID)
percentage := w.achievementMgr.GetCompletionPercentage(characterID, achievementID)
characterData[fmt.Sprintf("achievement_%d", achievementID)] = map[string]interface{}{
"id": achievementID,
"title": achievement.Title,
"description": achievement.UncompletedText,
"completed": completed,
"progress": progress,
"required": achievement.QtyRequired,
"percentage": percentage,
"points": achievement.PointValue,
"category": achievement.Category,
"expansion": achievement.Expansion,
}
}
fmt.Printf("Sending complete achievement list to %s: %d achievements\n",
client.CharacterName, len(allAchievements))
// Send packet to client (placeholder - would use actual packet building)
client.SendSimpleMessage(fmt.Sprintf("Character Achievements: %d total achievements", len(allAchievements)))
}
// calculateAchievementPoints calculates total achievement points for a character
func (w *World) calculateAchievementPoints(characterID int32) uint32 {
if w.achievementMgr == nil {
return 0
}
completedAchievements := w.achievementMgr.GetPlayerCompletedAchievements(characterID)
totalPoints := uint32(0)
for _, achievementID := range completedAchievements {
achievement := w.achievementMgr.GetAchievement(achievementID)
if achievement != nil {
totalPoints += achievement.PointValue
}
}
return totalPoints
}
// HandleTitleUpdate handles title update requests from client
func (w *World) HandleTitleUpdate(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s requested title update\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// Send current title data to client
w.SendTitleData(client)
}
return nil
}
// HandleCharacterTitles handles character titles request from client
func (w *World) HandleCharacterTitles(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s requested character titles\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// Send complete title list to client
w.SendCharacterTitles(client)
}
return nil
}
// HandleSetActiveTitle handles setting active title requests from client
func (w *World) HandleSetActiveTitle(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s requested to set active title\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse title ID and position from packet data
// TODO: Validate player has the title
// TODO: Set active title
// TODO: Send confirmation to client
// For now, just log the request
fmt.Printf("Set active title request for %s processed\n", client.CharacterName)
}
return nil
}
// SendTitleData sends title data to a client
func (w *World) SendTitleData(client *Client) {
if w.titleMgr == nil {
return
}
characterID := client.CharacterID
// Get player's titles
playerTitles := w.titleMgr.GetPlayerTitles(characterID)
titleCount := playerTitles.GetTitleCount()
fmt.Printf("Sending title data to %s: %d titles\n",
client.CharacterName, titleCount)
// Create title update packet (placeholder)
client.SendSimpleMessage(fmt.Sprintf("Title Update: %d titles available", titleCount))
}
// SendCharacterTitles sends complete character titles to client
func (w *World) SendCharacterTitles(client *Client) {
if w.titleMgr == nil {
return
}
characterID := client.CharacterID
// Get player's titles and master list
playerTitles := w.titleMgr.GetPlayerTitles(characterID)
masterList := w.titleMgr.titleManager.GetMasterList()
titleCount := playerTitles.GetTitleCount()
totalTitles := masterList.GetTitleCount()
fmt.Printf("Sending complete title list to %s: %d owned out of %d total\n",
client.CharacterName, titleCount, totalTitles)
// Get player's formatted name with titles
formattedName := w.titleMgr.GetPlayerFormattedName(characterID, client.CharacterName)
// Send packet to client (placeholder - would use actual packet building)
client.SendSimpleMessage(fmt.Sprintf("Character Titles: %d owned, %d total. Display name: %s",
titleCount, totalTitles, formattedName))
}
// NPC Packet Handlers
// HandleNPCAttack handles NPC attack packets from clients
func (w *World) HandleNPCAttack(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent NPC attack packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse NPC ID and attack type from packet data
// TODO: Validate player can attack NPC
// TODO: Process attack through combat system
// TODO: Send attack result to client and nearby players
// For now, just trigger a test NPC kill event for achievement testing
if w.npcMgr != nil {
testNPCID := int32(1001)
w.npcMgr.OnNPCKilled(testNPCID, ctx.Client.GetCharacterID())
}
}
return nil
}
// HandleNPCTarget handles NPC targeting packets from clients
func (w *World) HandleNPCTarget(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s sent NPC target packet\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse NPC ID from packet data
// TODO: Validate NPC exists and is targetable
// TODO: Set player's target
// TODO: Send targeting confirmation to client
// For testing, send NPC info for any targeting
w.SendNPCInfo(client, 1001) // Test NPC
}
return nil
}
// HandleNPCInfo handles NPC info requests from clients
func (w *World) HandleNPCInfo(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s requested NPC info\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse NPC ID from packet data
// TODO: Send NPC information to client
// For testing, send test NPC info
w.SendNPCInfo(client, 1001)
}
return nil
}
// HandleNPCSpellCast handles NPC spell cast notifications
func (w *World) HandleNPCSpellCast(ctx *packets.PacketContext, packet *packets.PacketData) error {
fmt.Printf("Client %s received NPC spell cast notification\n", ctx.Client.GetCharacterName())
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse spell cast data from packet
// TODO: Process spell effects
// TODO: Update client state based on spell effects
}
return nil
}
// HandleNPCMovement handles NPC movement updates
func (w *World) HandleNPCMovement(ctx *packets.PacketContext, packet *packets.PacketData) error {
// NPC movement updates can be frequent, so only log occasionally
client := w.clients.GetByCharacterID(ctx.Client.GetCharacterID())
if client != nil {
client.UpdateActivity()
// TODO: Parse NPC movement data from packet
// TODO: Update NPC position in world
// TODO: Send movement update to other clients in range
}
return nil
}
// SendNPCInfo sends NPC information to a client
func (w *World) SendNPCInfo(client *Client, npcID int32) {
if w.npcMgr == nil {
return
}
// Get NPC information
npcInfo := w.npcMgr.GetNPCInfo(npcID)
if npcInfo == nil {
return
}
fmt.Printf("Sending NPC info to %s: NPC %d (%s) Level %d\n",
client.CharacterName, npcInfo.ID, npcInfo.Name, npcInfo.Level)
// Get NPC statistics for additional info
stats := w.npcMgr.GetStatistics()
// Create NPC info packet (placeholder)
client.SendSimpleMessage(fmt.Sprintf("NPC Info: %s (ID: %d, Level: %d) - %v total NPCs active",
npcInfo.Name, npcInfo.ID, npcInfo.Level, stats["total_npcs"]))
}
// SendNPCUpdate sends NPC update to clients in range
func (w *World) SendNPCUpdate(npcID int32, updateType string, data map[string]interface{}) {
// TODO: Implement NPC update broadcasting
// This would send updates to all clients in range of the NPC
fmt.Printf("NPC Update: NPC %d - %s: %v\n", npcID, updateType, data)
// Get all clients and send update (placeholder)
clients := w.clients.GetAll()
for _, client := range clients {
if client.CurrentZone != nil {
// TODO: Check if client is in range of NPC
client.SendSimpleMessage(fmt.Sprintf("NPC Update: %s for NPC %d", updateType, npcID))
}
}
}
// SendNPCCombatUpdate sends combat-related NPC updates to clients
func (w *World) SendNPCCombatUpdate(npcID int32, targetID int32, combatType string, damage int32) {
// TODO: Implement NPC combat update broadcasting
fmt.Printf("NPC Combat Update: NPC %d -> Target %d, %s for %d damage\n",
npcID, targetID, combatType, damage)
// Send to relevant clients (placeholder)
clients := w.clients.GetAll()
for _, client := range clients {
if client.CurrentZone != nil && (client.CharacterID == targetID ||
client.CharacterID == npcID) { // TODO: Proper range check
client.SendSimpleMessage(fmt.Sprintf("Combat: NPC %d %s target %d for %d damage",
npcID, combatType, targetID, damage))
}
}
}
// WorldDatabaseAdapter adapts the World's database for packet handlers
type WorldDatabaseAdapter struct {
world *World
}
// GetCharacter implements packets.DatabaseInterface
func (wda *WorldDatabaseAdapter) GetCharacter(characterID int32) (map[string]interface{}, error) {
// TODO: Implement character loading from database
return nil, fmt.Errorf("character loading not yet implemented")
}
// SaveCharacter implements packets.DatabaseInterface
func (wda *WorldDatabaseAdapter) SaveCharacter(characterID int32, data map[string]interface{}) error {
// TODO: Implement character saving to database
return fmt.Errorf("character saving not yet implemented")
}
// WorldClientAdapter adapts World's Client to packets.ClientInterface
type WorldClientAdapter struct {
client *Client
world *World
}
// GetCharacterID implements packets.ClientInterface
func (wca *WorldClientAdapter) GetCharacterID() int32 {
return wca.client.CharacterID
}
// GetAccountID implements packets.ClientInterface
func (wca *WorldClientAdapter) GetAccountID() int32 {
return wca.client.AccountID
}
// GetCharacterName implements packets.ClientInterface
func (wca *WorldClientAdapter) GetCharacterName() string {
return wca.client.CharacterName
}
// GetClientVersion implements packets.ClientInterface
func (wca *WorldClientAdapter) GetClientVersion() int32 {
return wca.client.GetClientVersion()
}
// GetAdminLevel implements packets.ClientInterface
func (wca *WorldClientAdapter) GetAdminLevel() int {
return wca.client.AdminLevel
}
// IsInZone implements packets.ClientInterface
func (wca *WorldClientAdapter) IsInZone() bool {
return wca.client.CurrentZone != nil
}
// SendPacket implements packets.ClientInterface
func (wca *WorldClientAdapter) SendPacket(opcode packets.InternalOpcode, data []byte) error {
// TODO: Implement packet sending via UDP connection
fmt.Printf("Sending packet %s to client %s\n",
packets.GetInternalOpcodeName(opcode),
wca.client.CharacterName)
return nil
}
// Disconnect implements packets.ClientInterface
func (wca *WorldClientAdapter) Disconnect() error {
wca.client.DisconnectWithReason("Disconnected by packet handler")
return nil
}
// WorldServerAdapter adapts World to packets.WorldInterface
type WorldServerAdapter struct {
world *World
}
// GetClientByID implements packets.WorldInterface
func (wsa *WorldServerAdapter) GetClientByID(characterID int32) packets.ClientInterface {
client := wsa.world.clients.GetByCharacterID(characterID)
if client != nil {
return &WorldClientAdapter{client: client, world: wsa.world}
}
return nil
}
// GetAllClients implements packets.WorldInterface
func (wsa *WorldServerAdapter) GetAllClients() []packets.ClientInterface {
clients := wsa.world.clients.GetAll()
result := make([]packets.ClientInterface, len(clients))
for i, client := range clients {
result[i] = &WorldClientAdapter{client: client, world: wsa.world}
}
return result
}
// BroadcastPacket implements packets.WorldInterface
func (wsa *WorldServerAdapter) BroadcastPacket(opcode packets.InternalOpcode, data []byte) {
// TODO: Implement packet broadcasting
fmt.Printf("Broadcasting packet %s to all clients\n", packets.GetInternalOpcodeName(opcode))
}
// BroadcastToZone implements packets.WorldInterface
func (wsa *WorldServerAdapter) BroadcastToZone(zoneID int32, opcode packets.InternalOpcode, data []byte) {
// TODO: Implement zone-specific broadcasting
fmt.Printf("Broadcasting packet %s to zone %d\n", packets.GetInternalOpcodeName(opcode), zoneID)
}
// CreatePacketContext creates a packet context for a client
func (w *World) CreatePacketContext(client *Client) *packets.PacketContext {
return &packets.PacketContext{
Client: &WorldClientAdapter{client: client, world: w},
World: &WorldServerAdapter{world: w},
Database: &WorldDatabaseAdapter{world: w},
}
}