eq2go/internal/player/player.go

823 lines
21 KiB
Go

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