eq2go/internal/factions/player_faction.go

350 lines
9.2 KiB
Go

package factions
import (
"sync"
)
// PlayerFaction manages faction standing for a single player
type PlayerFaction struct {
factionValues map[int32]int32 // Faction ID -> current value
factionPercent map[int32]int8 // Faction ID -> percentage within con level
factionUpdateNeeded []int32 // Factions that need client updates
masterFactionList *MasterList
updateMutex sync.Mutex // Thread safety for updates
mutex sync.RWMutex // Thread safety for faction data
}
// NewPlayerFaction creates a new player faction system
func NewPlayerFaction(masterFactionList *MasterList) *PlayerFaction {
return &PlayerFaction{
factionValues: make(map[int32]int32),
factionPercent: make(map[int32]int8),
factionUpdateNeeded: make([]int32, 0),
masterFactionList: masterFactionList,
}
}
// GetMaxValue returns the maximum faction value for a given consideration level
func (pf *PlayerFaction) GetMaxValue(con int8) int32 {
if con < 0 {
return int32(con) * ConMultiplier
}
return (int32(con) * ConMultiplier) + ConRemainder
}
// GetMinValue returns the minimum faction value for a given consideration level
func (pf *PlayerFaction) GetMinValue(con int8) int32 {
if con <= 0 {
return (int32(con) * ConMultiplier) - ConRemainder
}
return int32(con) * ConMultiplier
}
// ShouldAttack returns true if the player should attack based on faction
func (pf *PlayerFaction) ShouldAttack(factionID int32) bool {
return pf.GetCon(factionID) <= AttackThreshold
}
// GetCon returns the consideration level (-4 to 4) for a faction
func (pf *PlayerFaction) GetCon(factionID int32) int8 {
// Special faction IDs have predefined cons
if factionID <= SpecialFactionIDMax {
if factionID == 0 {
return ConIndiff
}
return int8(factionID - 5)
}
value := pf.GetFactionValue(factionID)
// Neutral range
if value >= ConNeutralMin && value <= ConNeutralMax {
return ConIndiff
}
// Maximum ally
if value >= ConAllyMin {
return ConAlly
}
// Maximum hostile
if value <= ConHostileMax {
return ConKOS
}
// Calculate con based on value
return int8(value / ConMultiplier)
}
// GetPercent returns the percentage within the current consideration level
func (pf *PlayerFaction) GetPercent(factionID int32) int8 {
// Special factions have no percentage
if factionID <= SpecialFactionIDMax {
return 0
}
con := pf.GetCon(factionID)
value := pf.GetFactionValue(factionID)
if con != ConIndiff {
// Make value positive for calculation
if value <= 0 {
value *= -1
}
// Make con positive for calculation
if con < 0 {
con *= -1
}
// Calculate percentage within the con level
value -= int32(con) * ConMultiplier
value *= PercentMultiplier
return int8(value / ConMultiplier)
} else {
// Neutral range calculation
value += PercentNeutralOffset
value *= PercentMultiplier
return int8(value / PercentNeutralDivisor)
}
}
// FactionUpdate builds a faction update packet for the client
func (pf *PlayerFaction) FactionUpdate(version int16) ([]byte, error) {
pf.updateMutex.Lock()
defer pf.updateMutex.Unlock()
if len(pf.factionUpdateNeeded) == 0 {
return nil, nil
}
// This is a placeholder for packet building
// In the full implementation, this would use the PacketStruct system:
// packet := configReader.getStruct("WS_FactionUpdate", version)
// packet.setArrayLengthByName("num_factions", len(pf.factionUpdateNeeded))
// for i, factionID := range pf.factionUpdateNeeded {
// faction := pf.masterFactionList.GetFaction(factionID)
// if faction != nil {
// packet.setArrayDataByName("faction_id", faction.ID, i)
// packet.setArrayDataByName("name", faction.Name, i)
// packet.setArrayDataByName("description", faction.Description, i)
// packet.setArrayDataByName("category", faction.Type, i)
// packet.setArrayDataByName("con", pf.GetCon(faction.ID), i)
// packet.setArrayDataByName("percentage", pf.GetPercent(faction.ID), i)
// packet.setArrayDataByName("value", pf.GetFactionValue(faction.ID), i)
// }
// }
// return packet.serialize()
// Clear update list
pf.factionUpdateNeeded = pf.factionUpdateNeeded[:0]
// Return empty packet for now
return make([]byte, 0), nil
}
// GetFactionValue returns the current faction value for a faction
func (pf *PlayerFaction) GetFactionValue(factionID int32) int32 {
// Special factions always return 0
if factionID <= SpecialFactionIDMax {
return 0
}
pf.mutex.RLock()
defer pf.mutex.RUnlock()
// Return current value or 0 if not set
// Note: The C++ code has a comment about always returning the default value,
// but the actual implementation returns the stored value or 0
return pf.factionValues[factionID]
}
// ShouldIncrease returns true if the faction can be increased
func (pf *PlayerFaction) ShouldIncrease(factionID int32) bool {
if factionID <= SpecialFactionIDMax {
return false
}
if pf.masterFactionList == nil {
return false
}
return pf.masterFactionList.GetIncreaseAmount(factionID) != 0
}
// ShouldDecrease returns true if the faction can be decreased
func (pf *PlayerFaction) ShouldDecrease(factionID int32) bool {
if factionID <= SpecialFactionIDMax {
return false
}
if pf.masterFactionList == nil {
return false
}
return pf.masterFactionList.GetDecreaseAmount(factionID) != 0
}
// IncreaseFaction increases a faction value
func (pf *PlayerFaction) IncreaseFaction(factionID int32, amount int32) bool {
// Special factions cannot be changed
if factionID <= SpecialFactionIDMax {
return true
}
pf.mutex.Lock()
defer pf.mutex.Unlock()
// Use default amount if not specified
if amount == 0 && pf.masterFactionList != nil {
amount = pf.masterFactionList.GetIncreaseAmount(factionID)
}
// Increase the faction value
pf.factionValues[factionID] += amount
canContinue := true
// Cap at maximum value
if pf.factionValues[factionID] >= MaxFactionValue {
pf.factionValues[factionID] = MaxFactionValue
canContinue = false
}
// Mark for update
pf.addFactionUpdateNeeded(factionID)
return canContinue
}
// DecreaseFaction decreases a faction value
func (pf *PlayerFaction) DecreaseFaction(factionID int32, amount int32) bool {
// Special factions cannot be changed
if factionID <= SpecialFactionIDMax {
return true
}
pf.mutex.Lock()
defer pf.mutex.Unlock()
// Use default amount if not specified
if amount == 0 && pf.masterFactionList != nil {
amount = pf.masterFactionList.GetDecreaseAmount(factionID)
}
// Cannot decrease if no amount specified
if amount == 0 {
return false
}
// Decrease the faction value
pf.factionValues[factionID] -= amount
canContinue := true
// Cap at minimum value
if pf.factionValues[factionID] <= MinFactionValue {
pf.factionValues[factionID] = MinFactionValue
canContinue = false
}
// Mark for update
pf.addFactionUpdateNeeded(factionID)
return canContinue
}
// SetFactionValue sets a faction to a specific value
func (pf *PlayerFaction) SetFactionValue(factionID int32, value int32) bool {
pf.mutex.Lock()
defer pf.mutex.Unlock()
pf.factionValues[factionID] = value
// Mark for update
pf.addFactionUpdateNeeded(factionID)
return true
}
// GetFactionValues returns a copy of all faction values
func (pf *PlayerFaction) GetFactionValues() map[int32]int32 {
pf.mutex.RLock()
defer pf.mutex.RUnlock()
// Return a copy to prevent external modification
result := make(map[int32]int32)
for id, value := range pf.factionValues {
result[id] = value
}
return result
}
// HasFaction returns true if the player has a value for the given faction
func (pf *PlayerFaction) HasFaction(factionID int32) bool {
pf.mutex.RLock()
defer pf.mutex.RUnlock()
_, exists := pf.factionValues[factionID]
return exists
}
// GetFactionCount returns the number of factions the player has values for
func (pf *PlayerFaction) GetFactionCount() int {
pf.mutex.RLock()
defer pf.mutex.RUnlock()
return len(pf.factionValues)
}
// ClearFactionValues removes all faction values
func (pf *PlayerFaction) ClearFactionValues() {
pf.mutex.Lock()
defer pf.mutex.Unlock()
pf.factionValues = make(map[int32]int32)
pf.factionPercent = make(map[int32]int8)
}
// addFactionUpdateNeeded marks a faction as needing an update (internal use, assumes lock held)
func (pf *PlayerFaction) addFactionUpdateNeeded(factionID int32) {
// Note: This method assumes the mutex is already held by the caller
pf.updateMutex.Lock()
defer pf.updateMutex.Unlock()
pf.factionUpdateNeeded = append(pf.factionUpdateNeeded, factionID)
}
// GetPendingUpdates returns factions that need client updates
func (pf *PlayerFaction) GetPendingUpdates() []int32 {
pf.updateMutex.Lock()
defer pf.updateMutex.Unlock()
if len(pf.factionUpdateNeeded) == 0 {
return nil
}
// Return a copy
result := make([]int32, len(pf.factionUpdateNeeded))
copy(result, pf.factionUpdateNeeded)
return result
}
// ClearPendingUpdates clears the pending update list
func (pf *PlayerFaction) ClearPendingUpdates() {
pf.updateMutex.Lock()
defer pf.updateMutex.Unlock()
pf.factionUpdateNeeded = pf.factionUpdateNeeded[:0]
}
// HasPendingUpdates returns true if there are pending faction updates
func (pf *PlayerFaction) HasPendingUpdates() bool {
pf.updateMutex.Lock()
defer pf.updateMutex.Unlock()
return len(pf.factionUpdateNeeded) > 0
}