546 lines
18 KiB
Go
546 lines
18 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)
|
|
|
|
fmt.Printf("Registered %d packet handlers\n", 16)
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
|
|
// 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},
|
|
}
|
|
} |