package player import ( "sync" "eq2emu/internal/common" "eq2emu/internal/entity" "eq2emu/internal/quests" ) // Global XP table var levelXPReq map[int8]int32 var xpTableOnce sync.Once // NewPlayer creates a new player instance func NewPlayer() *Player { p := &Player{ charID: 0, spawnID: 1, spawnIndex: 1, tutorialStep: 0, packetNum: 0, posPacketSpeed: 0, houseVaultSlots: 0, // Initialize maps playerQuests: make(map[int32]*quests.Quest), completedQuests: make(map[int32]*quests.Quest), pendingQuests: make(map[int32]*quests.Quest), currentQuestFlagged: make(map[*entity.Spawn]bool), playerSpawnQuestsRequired: make(map[int32][]int32), playerSpawnHistoryRequired: make(map[int32][]int32), spawnVisPacketList: make(map[int32]string), spawnInfoPacketList: make(map[int32]string), spawnPosPacketList: make(map[int32]string), spawnPacketSent: make(map[int32]int8), spawnStateList: make(map[int32]*SpawnQueueState), playerSpawnIDMap: make(map[int32]*entity.Spawn), playerSpawnReverseIDMap: make(map[*entity.Spawn]int32), playerAggroRangeSpawns: make(map[int32]bool), pendingLootItems: make(map[int32]map[int32]bool), friendList: make(map[string]int8), ignoreList: make(map[string]int8), characterHistory: make(map[int8]map[int8][]*HistoryData), charLuaHistory: make(map[int32]*LUAHistory), playersPoiList: make(map[int32][]int32), pendingSelectableItemRewards: make(map[int32][]Item), statistics: make(map[int32]*Statistic), mailList: make(map[int32]*Mail), targetInvisHistory: make(map[int32]bool), spawnedBots: make(map[int32]int32), macroIcons: make(map[int32]int16), sortedTraitList: make(map[int8]map[int8][]*TraitData), classTraining: make(map[int8][]*TraitData), raceTraits: make(map[int8][]*TraitData), innateRaceTraits: make(map[int8][]*TraitData), focusEffects: make(map[int8][]*TraitData), } // Initialize entity base p.Entity = *entity.NewEntity() // Set player-specific defaults p.SetSpawnType(4) // Player spawn type p.appearance.DisplayName = 1 p.appearance.ShowCommandIcon = 1 p.appearance.PlayerFlag = 1 p.appearance.Targetable = 1 p.appearance.ShowLevel = 1 // Set default away message p.awayMessage = "Sorry, I am A.F.K. (Away From Keyboard)" // Add player-specific commands p.AddSecondaryEntityCommand("Inspect", 10000, "inspect_player", "", 0, 0) p.AddSecondaryEntityCommand("Who", 10000, "who", "", 0, 0) // Initialize self in spawn maps p.playerSpawnIDMap[1] = &p.Entity.Spawn p.playerSpawnReverseIDMap[&p.Entity.Spawn] = 1 // Set save spell effects p.stopSaveSpellEffects = false // Initialize trait update flag p.needTraitUpdate.Store(true) // Initialize control flags p.controlFlags = PlayerControlFlags{ flagChanges: make(map[int8]map[int8]int8), currentFlags: make(map[int8]map[int8]bool), } // Initialize character instances p.characterInstances = CharacterInstances{ instanceList: make([]InstanceData, 0), } return p } // GetClient returns the player's client connection func (p *Player) GetClient() *Client { return p.client } // SetClient sets the player's client connection func (p *Player) SetClient(client *Client) { p.client = client } // GetPlayerInfo returns the player's info structure, creating it if needed func (p *Player) GetPlayerInfo() *PlayerInfo { if p.info == nil { p.info = NewPlayerInfo(p) } return p.info } // IsPlayer always returns true for Player type func (p *Player) IsPlayer() bool { return true } // GetCharacterID returns the character's database ID func (p *Player) GetCharacterID() int32 { return p.charID } // SetCharacterID sets the character's database ID func (p *Player) SetCharacterID(id int32) { p.charID = id } // GetTutorialStep returns the current tutorial step func (p *Player) GetTutorialStep() int8 { return p.tutorialStep } // SetTutorialStep sets the current tutorial step func (p *Player) SetTutorialStep(step int8) { p.tutorialStep = step } // SetCharSheetChanged marks the character sheet as needing an update func (p *Player) SetCharSheetChanged(val bool) { p.charsheetChanged.Store(val) } // GetCharSheetChanged returns whether the character sheet needs updating func (p *Player) GetCharSheetChanged() bool { return p.charsheetChanged.Load() } // SetRaidSheetChanged marks the raid sheet as needing an update func (p *Player) SetRaidSheetChanged(val bool) { p.raidsheetChanged.Store(val) } // GetRaidSheetChanged returns whether the raid sheet needs updating func (p *Player) GetRaidSheetChanged() bool { return p.raidsheetChanged.Load() } // AddFriend adds a friend to the player's friend list func (p *Player) AddFriend(name string, save bool) { p.friendList[name] = 1 if save { // TODO: Save to database } } // IsFriend checks if a name is in the friend list func (p *Player) IsFriend(name string) bool { _, exists := p.friendList[name] return exists } // RemoveFriend removes a friend from the friend list func (p *Player) RemoveFriend(name string) { delete(p.friendList, name) // TODO: Remove from database } // GetFriends returns the friend list func (p *Player) GetFriends() map[string]int8 { return p.friendList } // AddIgnore adds a player to the ignore list func (p *Player) AddIgnore(name string, save bool) { p.ignoreList[name] = 1 if save { // TODO: Save to database } } // IsIgnored checks if a player is ignored func (p *Player) IsIgnored(name string) bool { _, exists := p.ignoreList[name] return exists } // RemoveIgnore removes a player from the ignore list func (p *Player) RemoveIgnore(name string) { delete(p.ignoreList, name) // TODO: Remove from database } // GetIgnoredPlayers returns the ignore list func (p *Player) GetIgnoredPlayers() map[string]int8 { return p.ignoreList } // GetPlayerDiscoveredPOIs returns the player's discovered POIs func (p *Player) GetPlayerDiscoveredPOIs() map[int32][]int32 { return p.playersPoiList } // AddPlayerDiscoveredPOI adds a discovered POI for the player func (p *Player) AddPlayerDiscoveredPOI(locationID int32) { // TODO: Implement POI discovery logic if p.playersPoiList[locationID] == nil { p.playersPoiList[locationID] = make([]int32, 0) } } // SetSideSpeed sets the player's side movement speed func (p *Player) SetSideSpeed(sideSpeed float32, updateFlags bool) { p.SetPos(&p.appearance.Pos.SideSpeed, sideSpeed, updateFlags) } // GetSideSpeed returns the player's side movement speed func (p *Player) GetSideSpeed() float32 { return p.appearance.Pos.SideSpeed } // SetVertSpeed sets the player's vertical movement speed func (p *Player) SetVertSpeed(vertSpeed float32, updateFlags bool) { p.SetPos(&p.appearance.Pos.VertSpeed, vertSpeed, updateFlags) } // GetVertSpeed returns the player's vertical movement speed func (p *Player) GetVertSpeed() float32 { return p.appearance.Pos.VertSpeed } // SetClientHeading1 sets the client heading 1 func (p *Player) SetClientHeading1(heading float32, updateFlags bool) { p.SetPos(&p.appearance.Pos.ClientHeading1, heading, updateFlags) } // GetClientHeading1 returns the client heading 1 func (p *Player) GetClientHeading1() float32 { return p.appearance.Pos.ClientHeading1 } // SetClientHeading2 sets the client heading 2 func (p *Player) SetClientHeading2(heading float32, updateFlags bool) { p.SetPos(&p.appearance.Pos.ClientHeading2, heading, updateFlags) } // GetClientHeading2 returns the client heading 2 func (p *Player) GetClientHeading2() float32 { return p.appearance.Pos.ClientHeading2 } // SetClientPitch sets the client pitch func (p *Player) SetClientPitch(pitch float32, updateFlags bool) { p.SetPos(&p.appearance.Pos.ClientPitch, pitch, updateFlags) } // GetClientPitch returns the client pitch func (p *Player) GetClientPitch() float32 { return p.appearance.Pos.ClientPitch } // IsResurrecting returns whether the player is currently resurrecting func (p *Player) IsResurrecting() bool { return p.resurrecting } // SetResurrecting sets the player's resurrection state func (p *Player) SetResurrecting(val bool) { p.resurrecting = val } // GetAwayMessage returns the player's away message func (p *Player) GetAwayMessage() string { return p.awayMessage } // SetAwayMessage sets the player's away message func (p *Player) SetAwayMessage(message string) { p.awayMessage = message } // GetIsTracking returns whether the player is tracking func (p *Player) GetIsTracking() bool { return p.isTracking } // SetIsTracking sets the player's tracking state func (p *Player) SetIsTracking(val bool) { p.isTracking = val } // GetBiography returns the player's biography func (p *Player) GetBiography() string { return p.biography } // SetBiography sets the player's biography func (p *Player) SetBiography(bio string) { p.biography = bio } // GetGuild returns the player's guild func (p *Player) GetGuild() *Guild { return p.guild } // SetGuild sets the player's guild func (p *Player) SetGuild(guild *Guild) { p.guild = guild } // SetPendingDeletion marks the player for deletion func (p *Player) SetPendingDeletion(val bool) { p.pendingDeletion = val } // GetPendingDeletion returns whether the player is marked for deletion func (p *Player) GetPendingDeletion() bool { return p.pendingDeletion } // GetPosPacketSpeed returns the position packet speed func (p *Player) GetPosPacketSpeed() float32 { return p.posPacketSpeed } // IsReturningFromLD returns whether the player is returning from linkdead func (p *Player) IsReturningFromLD() bool { return p.returningFromLD } // SetReturningFromLD sets whether the player is returning from linkdead func (p *Player) SetReturningFromLD(val bool) { p.returningFromLD = val } // GetLastMovementActivity returns the last movement activity value func (p *Player) GetLastMovementActivity() int16 { return p.lastMovementActivity } // SetRangeAttack sets whether the player is using ranged attacks func (p *Player) SetRangeAttack(val bool) { p.rangeAttack = val } // GetRangeAttack returns whether the player is using ranged attacks func (p *Player) GetRangeAttack() bool { return p.rangeAttack } // HasGMVision returns whether the player has GM vision enabled func (p *Player) HasGMVision() bool { return p.gmVision } // SetGMVision sets the player's GM vision state func (p *Player) SetGMVision(val bool) { p.gmVision = val } // GetCurrentLanguage returns the player's current language ID func (p *Player) GetCurrentLanguage() int32 { return p.currentLanguageID } // SetCurrentLanguage sets the player's current language ID func (p *Player) SetCurrentLanguage(languageID int32) { p.currentLanguageID = languageID } // SetActiveReward sets whether the player has an active reward func (p *Player) SetActiveReward(val bool) { p.activeReward = val } // IsActiveReward returns whether the player has an active reward func (p *Player) IsActiveReward() bool { return p.activeReward } // GetActiveFoodUniqueID returns the active food item's unique ID func (p *Player) GetActiveFoodUniqueID() int64 { return p.activeFoodUniqueID.Load() } // SetActiveFoodUniqueID sets the active food item's unique ID func (p *Player) SetActiveFoodUniqueID(uniqueID int32, updateDB bool) { p.activeFoodUniqueID.Store(int64(uniqueID)) if updateDB { // TODO: Update database } } // GetActiveDrinkUniqueID returns the active drink item's unique ID func (p *Player) GetActiveDrinkUniqueID() int64 { return p.activeDrinkUniqueID.Load() } // SetActiveDrinkUniqueID sets the active drink item's unique ID func (p *Player) SetActiveDrinkUniqueID(uniqueID int32, updateDB bool) { p.activeDrinkUniqueID.Store(int64(uniqueID)) if updateDB { // TODO: Update database } } // GetHouseVaultSlots returns the number of house vault slots func (p *Player) GetHouseVaultSlots() int8 { return p.houseVaultSlots } // SetHouseVaultSlots sets the number of house vault slots func (p *Player) SetHouseVaultSlots(slots int8) { p.houseVaultSlots = slots } // GetCurrentRecipe returns the current recipe ID func (p *Player) GetCurrentRecipe() int32 { return p.currentRecipe } // SetCurrentRecipe sets the current recipe ID func (p *Player) SetCurrentRecipe(recipeID int32) { p.currentRecipe = recipeID } // GetTempMount returns the temporary mount model ID func (p *Player) GetTempMount() int32 { return p.tmpMountModel } // SetTempMount sets the temporary mount model ID func (p *Player) SetTempMount(id int32) { p.tmpMountModel = id } // GetTempMountColor returns the temporary mount color func (p *Player) GetTempMountColor() common.EQ2Color { return p.tmpMountColor } // SetTempMountColor sets the temporary mount color func (p *Player) SetTempMountColor(color *common.EQ2Color) { p.tmpMountColor = *color } // GetTempMountSaddleColor returns the temporary mount saddle color func (p *Player) GetTempMountSaddleColor() common.EQ2Color { return p.tmpMountSaddleColor } // SetTempMountSaddleColor sets the temporary mount saddle color func (p *Player) SetTempMountSaddleColor(color *common.EQ2Color) { p.tmpMountSaddleColor = *color } // StopSaveSpellEffects returns whether to stop saving spell effects func (p *Player) StopSaveSpellEffects() bool { return p.stopSaveSpellEffects } // SetSaveSpellEffects sets whether to save spell effects func (p *Player) SetSaveSpellEffects(val bool) { p.stopSaveSpellEffects = !val } // ResetMentorship checks and resets mentorship if needed func (p *Player) ResetMentorship() bool { mentorshipStatus := p.resetMentorship if mentorshipStatus { p.SetMentorStats(p.GetLevel(), 0, true) } p.resetMentorship = false return mentorshipStatus } // EnableResetMentorship enables mentorship reset func (p *Player) EnableResetMentorship() { p.resetMentorship = true } // StopCombat stops all combat based on type func (p *Player) StopCombat(combatType int8) { switch combatType { case 2: p.SetRangeAttack(false) p.InCombat(false, true) default: p.InCombat(false, false) p.InCombat(false, true) p.SetRangeAttack(false) } } // GetPVPAlignment returns the player's PVP alignment func (p *Player) GetPVPAlignment() int { // TODO: Implement PVP alignment logic return 0 } // InitXPTable initializes the global XP requirements table func InitXPTable() { xpTableOnce.Do(func() { levelXPReq = make(map[int8]int32) // TODO: Load XP requirements from database or config // For now, using placeholder values for i := int8(1); i <= 100; i++ { levelXPReq[i] = int32(i * 1000) } }) } // GetNeededXPByLevel returns the XP needed for a specific level func GetNeededXPByLevel(level int8) int32 { InitXPTable() if xp, exists := levelXPReq[level]; exists { return xp } return 0 } // Cleanup performs cleanup when the player is being destroyed func (p *Player) Cleanup() { // Set save spell effects p.SetSaveSpellEffects(true) // Clear spells for _, spell := range p.spells { spell = nil } p.spells = nil // Clear quickbar for _, item := range p.quickbarItems { item = nil } p.quickbarItems = nil // Clear quest spawn requirements p.playerSpawnQuestsRequiredMutex.Lock() for _, list := range p.playerSpawnQuestsRequired { list = nil } p.playerSpawnQuestsRequired = nil p.playerSpawnQuestsRequiredMutex.Unlock() // Clear history spawn requirements p.playerSpawnHistoryRequiredMutex.Lock() for _, list := range p.playerSpawnHistoryRequired { list = nil } p.playerSpawnHistoryRequired = nil p.playerSpawnHistoryRequiredMutex.Unlock() // Clear character history for _, typeMap := range p.characterHistory { for _, histList := range typeMap { for _, hist := range histList { hist = nil } } } p.characterHistory = nil // Clear LUA history p.luaHistoryMutex.Lock() for _, hist := range p.charLuaHistory { hist = nil } p.charLuaHistory = nil p.luaHistoryMutex.Unlock() // Clear movement packets p.movementPacket = nil p.oldMovementPacket = nil p.spawnTmpInfoXorPacket = nil p.spawnTmpVisXorPacket = nil p.spawnTmpPosXorPacket = nil p.spellXorPacket = nil p.spellOrigPacket = nil p.raidOrigPacket = nil p.raidXorPacket = nil // Destroy quests p.DestroyQuests() // Write and remove statistics p.WritePlayerStatistics() p.RemovePlayerStatistics() // Delete mail p.DeleteMail(false) // TODO: Remove from world lotto // world.RemoveLottoPlayer(p.GetCharacterID()) // Clear player info p.info = nil // Clear spawn maps p.indexMutex.Lock() p.playerSpawnReverseIDMap = nil p.playerSpawnIDMap = nil p.indexMutex.Unlock() // Clear packet lists p.infoMutex.Lock() p.spawnInfoPacketList = nil p.infoMutex.Unlock() p.visMutex.Lock() p.spawnVisPacketList = nil p.visMutex.Unlock() p.posMutex.Lock() p.spawnPosPacketList = nil p.posMutex.Unlock() // Clear packet structures p.spawnHeaderStruct = nil p.spawnFooterStruct = nil p.signFooterStruct = nil p.widgetFooterStruct = nil p.spawnInfoStruct = nil p.spawnVisStruct = nil p.spawnPosStruct = nil // Clear pending rewards p.ClearPendingSelectableItemRewards(0, true) p.ClearPendingItemRewards() // Clear everything else p.ClearEverything() // Clear traits p.sortedTraitList = nil p.classTraining = nil p.raceTraits = nil p.innateRaceTraits = nil p.focusEffects = nil // Clear language list p.playerLanguagesList.Clear() } // DestroyQuests cleans up all quest data func (p *Player) DestroyQuests() { p.playerQuestsMutex.Lock() defer p.playerQuestsMutex.Unlock() // Clear completed quests for id, quest := range p.completedQuests { delete(p.completedQuests, id) _ = quest } // Clear active quests for id, quest := range p.playerQuests { delete(p.playerQuests, id) _ = quest } // Clear pending quests for id, quest := range p.pendingQuests { delete(p.pendingQuests, id) _ = quest } } // WritePlayerStatistics saves player statistics to database func (p *Player) WritePlayerStatistics() { // TODO: Implement database write } // RemovePlayerStatistics removes all player statistics func (p *Player) RemovePlayerStatistics() { for id := range p.statistics { delete(p.statistics, id) } } // DeleteMail deletes all mail func (p *Player) DeleteMail(fromDatabase bool) { p.mailMutex.Lock() defer p.mailMutex.Unlock() for id, mail := range p.mailList { if fromDatabase { // TODO: Delete from database } delete(p.mailList, id) _ = mail } } // ClearPendingSelectableItemRewards clears pending selectable item rewards func (p *Player) ClearPendingSelectableItemRewards(sourceID int32, all bool) { if all { for id, items := range p.pendingSelectableItemRewards { for _, item := range items { _ = item } delete(p.pendingSelectableItemRewards, id) } } else if sourceID > 0 { if items, exists := p.pendingSelectableItemRewards[sourceID]; exists { for _, item := range items { _ = item } delete(p.pendingSelectableItemRewards, sourceID) } } } // ClearPendingItemRewards clears all pending item rewards func (p *Player) ClearPendingItemRewards() { for i := range p.pendingItemRewards { p.pendingItemRewards[i] = Item{} } p.pendingItemRewards = nil } // ClearEverything performs final cleanup func (p *Player) ClearEverything() { // TODO: Implement final cleanup logic } // GetCharacterInstances returns the character instances manager func (p *Player) GetCharacterInstances() *CharacterInstances { return &p.characterInstances } // GetFactions returns the player's faction manager func (p *Player) GetFactions() *PlayerFaction { return &p.factions } // SetFactionValue sets a faction value for the player func (p *Player) SetFactionValue(factionID int32, value int32) { p.factions.SetFactionValue(factionID, value) } // GetCollectionList returns the player's collection list func (p *Player) GetCollectionList() *PlayerCollectionList { return &p.collectionList } // GetRecipeList returns the player's recipe list func (p *Player) GetRecipeList() *PlayerRecipeList { return &p.recipeList } // GetRecipeBookList returns the player's recipe book list func (p *Player) GetRecipeBookList() *PlayerRecipeBookList { return &p.recipebookList } // GetAchievementList returns the player's achievement list func (p *Player) GetAchievementList() *PlayerAchievementList { return &p.achievementList } // GetAchievementUpdateList returns the player's achievement update list func (p *Player) GetAchievementUpdateList() *PlayerAchievementUpdateList { return &p.achievementUpdateList } // GetPlayerTitles returns the player's titles list func (p *Player) GetPlayerTitles() *PlayerTitlesList { return &p.playerTitlesList } // GetPlayerLanguages returns the player's languages list func (p *Player) GetPlayerLanguages() *PlayerLanguagesList { return &p.playerLanguagesList } // GetPlayerItemList returns the player's item list func (p *Player) GetPlayerItemList() *PlayerItemList { return &p.itemList } // GetSkills returns the player's skill list func (p *Player) GetSkills() *PlayerSkillList { return &p.skillList } // AddGMVisualFilter adds a GM visual filter func (p *Player) AddGMVisualFilter(filterType, filterValue int32, filterSearchStr string, visualTag int16) { filter := GMTagFilter{ FilterType: filterType, FilterValue: filterValue, VisualTag: visualTag, } copy(filter.FilterSearchCriteria[:], filterSearchStr) p.gmVisualFilters = append(p.gmVisualFilters, filter) } // ClearGMVisualFilters clears all GM visual filters func (p *Player) ClearGMVisualFilters() { p.gmVisualFilters = nil } // macroIcons map - declared at package level since it was referenced but not in struct var macroIcons map[int32]int16