eq2go/internal/skills/player_skill_list.go

420 lines
10 KiB
Go

package skills
import (
"math/rand"
"sync"
)
// PlayerSkillList manages skills for a specific player
type PlayerSkillList struct {
skills map[int32]*Skill // Player's skills by ID
nameSkillMap map[string]*Skill // Skills by name for quick lookup
skillUpdates []*Skill // Skills needing updates
skillBonusList map[int32]*SkillBonus // Skill bonuses by spell ID
// Packet data for skill updates
origPacket []byte
xorPacket []byte
origPacketSize int16
packetCount int16
hasUpdates bool
mutex sync.RWMutex // Thread safety for skills/nameMap
updatesMutex sync.Mutex // Thread safety for updates
bonusMutex sync.RWMutex // Thread safety for bonuses
}
// NewPlayerSkillList creates a new player skill list
func NewPlayerSkillList() *PlayerSkillList {
return &PlayerSkillList{
skills: make(map[int32]*Skill),
nameSkillMap: make(map[string]*Skill),
skillUpdates: make([]*Skill, 0),
skillBonusList: make(map[int32]*SkillBonus),
hasUpdates: false,
}
}
// AddSkill adds a skill to the player's skill list
func (psl *PlayerSkillList) AddSkill(newSkill *Skill) {
if newSkill == nil {
return
}
psl.mutex.Lock()
defer psl.mutex.Unlock()
// Remove old skill if it exists
if oldSkill, exists := psl.skills[newSkill.SkillID]; exists {
// TODO: Set Lua user data stale when LuaInterface is integrated
_ = oldSkill
}
psl.skills[newSkill.SkillID] = newSkill
// Clear name map cache so it gets rebuilt
psl.nameSkillMap = make(map[string]*Skill)
}
// RemoveSkill removes a skill from the player's skill list
func (psl *PlayerSkillList) RemoveSkill(skill *Skill) {
if skill == nil {
return
}
psl.mutex.Lock()
defer psl.mutex.Unlock()
// TODO: Set Lua user data stale when LuaInterface is integrated
skill.ActiveSkill = false
// Clear name map cache
psl.nameSkillMap = make(map[string]*Skill)
}
// GetAllSkills returns all player skills
func (psl *PlayerSkillList) GetAllSkills() map[int32]*Skill {
psl.mutex.RLock()
defer psl.mutex.RUnlock()
// Return a copy to prevent external modification
skills := make(map[int32]*Skill)
for id, skill := range psl.skills {
skills[id] = skill
}
return skills
}
// HasSkill checks if player has a specific skill
func (psl *PlayerSkillList) HasSkill(skillID int32) bool {
psl.mutex.RLock()
defer psl.mutex.RUnlock()
skill, exists := psl.skills[skillID]
return exists && skill.ActiveSkill
}
// GetSkill returns a skill by ID
func (psl *PlayerSkillList) GetSkill(skillID int32) *Skill {
psl.mutex.RLock()
defer psl.mutex.RUnlock()
if skill, exists := psl.skills[skillID]; exists && skill.ActiveSkill {
return skill
}
return nil
}
// GetSkillByName returns a skill by name
func (psl *PlayerSkillList) GetSkillByName(name string) *Skill {
psl.mutex.Lock()
defer psl.mutex.Unlock()
// Build name map if empty
if len(psl.nameSkillMap) == 0 {
for _, skill := range psl.skills {
if skill.ActiveSkill {
psl.nameSkillMap[skill.Name.Data] = skill
}
}
}
if skill, exists := psl.nameSkillMap[name]; exists {
return skill
}
return nil
}
// IncreaseSkill increases a skill's current value
func (psl *PlayerSkillList) IncreaseSkill(skill *Skill, amount int16) {
if skill == nil {
return
}
skill.PreviousVal = skill.CurrentVal
skill.CurrentVal += amount
if skill.CurrentVal > skill.MaxVal {
skill.MaxVal = skill.CurrentVal
}
psl.AddSkillUpdateNeeded(skill)
skill.SaveNeeded = true
}
// IncreaseSkillByID increases a skill's current value by ID
func (psl *PlayerSkillList) IncreaseSkillByID(skillID int32, amount int16) {
skill := psl.GetSkill(skillID)
psl.IncreaseSkill(skill, amount)
}
// DecreaseSkill decreases a skill's current value
func (psl *PlayerSkillList) DecreaseSkill(skill *Skill, amount int16) {
if skill == nil {
return
}
skill.PreviousVal = skill.CurrentVal
if skill.CurrentVal < amount {
skill.CurrentVal = 0
} else {
skill.CurrentVal -= amount
}
skill.SaveNeeded = true
psl.AddSkillUpdateNeeded(skill)
}
// DecreaseSkillByID decreases a skill's current value by ID
func (psl *PlayerSkillList) DecreaseSkillByID(skillID int32, amount int16) {
skill := psl.GetSkill(skillID)
psl.DecreaseSkill(skill, amount)
}
// SetSkill sets a skill's current value
func (psl *PlayerSkillList) SetSkill(skill *Skill, value int16, sendUpdate bool) {
if skill == nil {
return
}
skill.PreviousVal = skill.CurrentVal
skill.CurrentVal = value
if skill.CurrentVal > skill.MaxVal {
skill.MaxVal = skill.CurrentVal
}
skill.SaveNeeded = true
if sendUpdate {
psl.AddSkillUpdateNeeded(skill)
}
}
// SetSkillByID sets a skill's current value by ID
func (psl *PlayerSkillList) SetSkillByID(skillID int32, value int16, sendUpdate bool) {
skill := psl.GetSkill(skillID)
psl.SetSkill(skill, value, sendUpdate)
}
// IncreaseSkillCap increases a skill's maximum value
func (psl *PlayerSkillList) IncreaseSkillCap(skill *Skill, amount int16) {
if skill == nil {
return
}
skill.MaxVal += amount
skill.SaveNeeded = true
}
// IncreaseSkillCapByID increases a skill's maximum value by ID
func (psl *PlayerSkillList) IncreaseSkillCapByID(skillID int32, amount int16) {
skill := psl.GetSkill(skillID)
psl.IncreaseSkillCap(skill, amount)
}
// DecreaseSkillCap decreases a skill's maximum value
func (psl *PlayerSkillList) DecreaseSkillCap(skill *Skill, amount int16) {
if skill == nil {
return
}
if skill.MaxVal < amount {
skill.MaxVal = 0
} else {
skill.MaxVal -= amount
}
// Adjust current value if it exceeds new max
if skill.CurrentVal > skill.MaxVal {
skill.PreviousVal = skill.CurrentVal
skill.CurrentVal = skill.MaxVal
}
psl.AddSkillUpdateNeeded(skill)
skill.SaveNeeded = true
}
// DecreaseSkillCapByID decreases a skill's maximum value by ID
func (psl *PlayerSkillList) DecreaseSkillCapByID(skillID int32, amount int16) {
skill := psl.GetSkill(skillID)
psl.DecreaseSkillCap(skill, amount)
}
// SetSkillCap sets a skill's maximum value
func (psl *PlayerSkillList) SetSkillCap(skill *Skill, value int16) {
if skill == nil {
return
}
skill.MaxVal = value
// Adjust current value if it exceeds new max
if skill.CurrentVal > skill.MaxVal {
skill.PreviousVal = skill.CurrentVal
skill.CurrentVal = skill.MaxVal
}
psl.AddSkillUpdateNeeded(skill)
skill.SaveNeeded = true
}
// SetSkillCapByID sets a skill's maximum value by ID
func (psl *PlayerSkillList) SetSkillCapByID(skillID int32, value int16) {
skill := psl.GetSkill(skillID)
psl.SetSkillCap(skill, value)
}
// SetSkillValuesByType sets all skills of a type to a specific value
func (psl *PlayerSkillList) SetSkillValuesByType(skillType int8, value int16, sendUpdate bool) {
psl.mutex.RLock()
defer psl.mutex.RUnlock()
for _, skill := range psl.skills {
if skill != nil && skill.SkillType == int32(skillType) {
psl.SetSkill(skill, value, sendUpdate)
}
}
}
// SetSkillCapsByType sets all skill caps of a type to a specific value
func (psl *PlayerSkillList) SetSkillCapsByType(skillType int8, value int16) {
psl.mutex.RLock()
defer psl.mutex.RUnlock()
for _, skill := range psl.skills {
if skill != nil && skill.SkillType == int32(skillType) {
psl.SetSkillCap(skill, value)
}
}
}
// IncreaseSkillCapsByType increases all skill caps of a type
func (psl *PlayerSkillList) IncreaseSkillCapsByType(skillType int8, value int16) {
psl.mutex.RLock()
defer psl.mutex.RUnlock()
for _, skill := range psl.skills {
if skill != nil && skill.SkillType == int32(skillType) {
psl.IncreaseSkillCap(skill, value)
}
}
}
// IncreaseAllSkillCaps increases all skill caps
func (psl *PlayerSkillList) IncreaseAllSkillCaps(value int16) {
psl.mutex.RLock()
defer psl.mutex.RUnlock()
for _, skill := range psl.skills {
if skill != nil {
psl.IncreaseSkillCap(skill, value)
}
}
}
// CheckSkillIncrease checks if a skill should increase and does so if successful
func (psl *PlayerSkillList) CheckSkillIncrease(skill *Skill) bool {
if skill == nil || skill.CurrentVal >= skill.MaxVal {
return false
}
// Calculate increase chance: skill level 1 = 20%, 100 = 10%, 400 = 4%
percent := int8((100.0 / float32(50 + skill.CurrentVal)) * 10.0)
if rand.Intn(100) < int(percent) {
psl.IncreaseSkill(skill, 1)
return true
}
return false
}
// AddSkillUpdateNeeded marks a skill as needing an update packet
func (psl *PlayerSkillList) AddSkillUpdateNeeded(skill *Skill) {
if skill == nil {
return
}
psl.updatesMutex.Lock()
defer psl.updatesMutex.Unlock()
psl.skillUpdates = append(psl.skillUpdates, skill)
psl.hasUpdates = true
}
// HasSkillUpdates returns whether there are pending skill updates
func (psl *PlayerSkillList) HasSkillUpdates() bool {
psl.updatesMutex.Lock()
defer psl.updatesMutex.Unlock()
return psl.hasUpdates
}
// GetSkillUpdates returns and clears pending skill updates
func (psl *PlayerSkillList) GetSkillUpdates() []*Skill {
psl.updatesMutex.Lock()
defer psl.updatesMutex.Unlock()
if len(psl.skillUpdates) == 0 {
return nil
}
updates := make([]*Skill, len(psl.skillUpdates))
copy(updates, psl.skillUpdates)
// Clear the updates
psl.skillUpdates = psl.skillUpdates[:0]
psl.hasUpdates = false
return updates
}
// GetSaveNeededSkills returns skills that need to be saved to database
func (psl *PlayerSkillList) GetSaveNeededSkills() []*Skill {
psl.mutex.RLock()
defer psl.mutex.RUnlock()
var saveNeeded []*Skill
for _, skill := range psl.skills {
if skill.SaveNeeded {
saveNeeded = append(saveNeeded, skill)
skill.SaveNeeded = false // Clear the flag
}
}
return saveNeeded
}
// ResetPackets clears cached packet data
func (psl *PlayerSkillList) ResetPackets() {
psl.updatesMutex.Lock()
defer psl.updatesMutex.Unlock()
psl.origPacket = nil
psl.xorPacket = nil
psl.origPacketSize = 0
psl.packetCount = 0
}
// GetSkillPacket builds a skill update packet for the client
func (psl *PlayerSkillList) GetSkillPacket(version int16) ([]byte, error) {
psl.mutex.Lock()
defer psl.mutex.Unlock()
// This is a placeholder implementation
// In the full implementation, this would use the PacketStruct system
// to build a WS_UpdateSkillBook packet with all player skills
// TODO: Implement packet building using PacketStruct system
// packet := configReader.getStruct("WS_UpdateSkillBook", version)
// [complex packet building logic here]
// For now, return empty packet
return make([]byte, 0), nil
}