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 *MasterFactionList 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 *MasterFactionList) *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 }