eq2go/internal/races/races.go

388 lines
9.6 KiB
Go

package races
import (
"math/rand"
"strings"
"sync"
)
// Races manages race information and lookups
// Converted from C++ Races class
type Races struct {
// Race name to ID mapping (uppercase keys)
raceMap map[string]int8
// ID to friendly name mapping
friendlyNameMap map[int8]string
// Alignment-based race lists for randomization
goodRaces []string
evilRaces []string
// Thread safety
mutex sync.RWMutex
}
// NewRaces creates a new races manager with all EQ2 races
// Converted from C++ Races::Races constructor
func NewRaces() *Races {
races := &Races{
raceMap: make(map[string]int8),
friendlyNameMap: make(map[int8]string),
goodRaces: make([]string, 0),
evilRaces: make([]string, 0),
}
races.initializeRaces()
return races
}
// initializeRaces sets up all race mappings
func (r *Races) initializeRaces() {
// Initialize race name to ID mappings (from C++ constructor)
r.raceMap[RaceNameBarbarian] = RaceBarbarian
r.raceMap[RaceNameDarkElf] = RaceDarkElf
r.raceMap[RaceNameDwarf] = RaceDwarf
r.raceMap[RaceNameErudite] = RaceErudite
r.raceMap[RaceNameFroglok] = RaceFroglok
r.raceMap[RaceNameGnome] = RaceGnome
r.raceMap[RaceNameHalfElf] = RaceHalfElf
r.raceMap[RaceNameHalfling] = RaceHalfling
r.raceMap[RaceNameHighElf] = RaceHighElf
r.raceMap[RaceNameHuman] = RaceHuman
r.raceMap[RaceNameIksar] = RaceIksar
r.raceMap[RaceNameKerra] = RaceKerra
r.raceMap[RaceNameOgre] = RaceOgre
r.raceMap[RaceNameRatonga] = RaceRatonga
r.raceMap[RaceNameTroll] = RaceTroll
r.raceMap[RaceNameWoodElf] = RaceWoodElf
r.raceMap[RaceNameFaeLight] = RaceFae
r.raceMap[RaceNameFaeDark] = RaceArasai
r.raceMap[RaceNameSarnak] = RaceSarnak
r.raceMap[RaceNameVampire] = RaceVampire
r.raceMap[RaceNameAerakyn] = RaceAerakyn
// Initialize friendly display names (from C++ constructor)
r.friendlyNameMap[RaceBarbarian] = DisplayNameBarbarian
r.friendlyNameMap[RaceDarkElf] = DisplayNameDarkElf
r.friendlyNameMap[RaceDwarf] = DisplayNameDwarf
r.friendlyNameMap[RaceErudite] = DisplayNameErudite
r.friendlyNameMap[RaceFroglok] = DisplayNameFroglok
r.friendlyNameMap[RaceGnome] = DisplayNameGnome
r.friendlyNameMap[RaceHalfElf] = DisplayNameHalfElf
r.friendlyNameMap[RaceHalfling] = DisplayNameHalfling
r.friendlyNameMap[RaceHighElf] = DisplayNameHighElf
r.friendlyNameMap[RaceHuman] = DisplayNameHuman
r.friendlyNameMap[RaceIksar] = DisplayNameIksar
r.friendlyNameMap[RaceKerra] = DisplayNameKerra
r.friendlyNameMap[RaceOgre] = DisplayNameOgre
r.friendlyNameMap[RaceRatonga] = DisplayNameRatonga
r.friendlyNameMap[RaceTroll] = DisplayNameTroll
r.friendlyNameMap[RaceWoodElf] = DisplayNameWoodElf
r.friendlyNameMap[RaceFae] = DisplayNameFae
r.friendlyNameMap[RaceArasai] = DisplayNameArasai
r.friendlyNameMap[RaceSarnak] = DisplayNameSarnak
r.friendlyNameMap[RaceVampire] = DisplayNameVampire
r.friendlyNameMap[RaceAerakyn] = DisplayNameAerakyn
// Initialize good races (from C++ race_map_good)
// "Neutral" races appear in both lists for /randomize functionality
r.goodRaces = []string{
RaceNameDwarf, // 0
RaceNameFaeLight, // 1
RaceNameFroglok, // 2
RaceNameHalfling, // 3
RaceNameHighElf, // 4
RaceNameWoodElf, // 5
RaceNameBarbarian, // 6 (neutral)
RaceNameErudite, // 7 (neutral)
RaceNameGnome, // 8 (neutral)
RaceNameHalfElf, // 9 (neutral)
RaceNameHuman, // 10 (neutral)
RaceNameKerra, // 11 (neutral)
RaceNameVampire, // 12 (neutral)
RaceNameAerakyn, // 13 (neutral)
}
// Initialize evil races (from C++ race_map_evil)
r.evilRaces = []string{
RaceNameFaeDark, // 0
RaceNameDarkElf, // 1
RaceNameIksar, // 2
RaceNameOgre, // 3
RaceNameRatonga, // 4
RaceNameSarnak, // 5
RaceNameTroll, // 6
RaceNameBarbarian, // 7 (neutral)
RaceNameErudite, // 8 (neutral)
RaceNameGnome, // 9 (neutral)
RaceNameHalfElf, // 10 (neutral)
RaceNameHuman, // 11 (neutral)
RaceNameKerra, // 12 (neutral)
RaceNameVampire, // 13 (neutral)
RaceNameAerakyn, // 14 (neutral)
}
}
// GetRaceID returns the race ID for a given race name
// Converted from C++ Races::GetRaceID
func (r *Races) GetRaceID(name string) int8 {
r.mutex.RLock()
defer r.mutex.RUnlock()
raceName := strings.ToUpper(strings.TrimSpace(name))
if raceID, exists := r.raceMap[raceName]; exists {
return raceID
}
return -1 // Invalid race
}
// GetRaceName returns the uppercase race name for a given ID
// Converted from C++ Races::GetRaceName
func (r *Races) GetRaceName(raceID int8) string {
r.mutex.RLock()
defer r.mutex.RUnlock()
// Search through race map to find the name
for name, id := range r.raceMap {
if id == raceID {
return name
}
}
return "" // Invalid race ID
}
// GetRaceNameCase returns the friendly display name for a given race ID
// Converted from C++ Races::GetRaceNameCase
func (r *Races) GetRaceNameCase(raceID int8) string {
r.mutex.RLock()
defer r.mutex.RUnlock()
if friendlyName, exists := r.friendlyNameMap[raceID]; exists {
return friendlyName
}
return "" // Invalid race ID
}
// GetRandomGoodRace returns a random good-aligned race ID
// Converted from C++ Races::GetRaceNameGood
func (r *Races) GetRandomGoodRace() int8 {
r.mutex.RLock()
defer r.mutex.RUnlock()
if len(r.goodRaces) == 0 {
return DefaultRaceID
}
randomIndex := rand.Intn(len(r.goodRaces))
raceName := r.goodRaces[randomIndex]
if raceID, exists := r.raceMap[raceName]; exists {
return raceID
}
return DefaultRaceID // Default to Human if error
}
// GetRandomEvilRace returns a random evil-aligned race ID
// Converted from C++ Races::GetRaceNameEvil
func (r *Races) GetRandomEvilRace() int8 {
r.mutex.RLock()
defer r.mutex.RUnlock()
if len(r.evilRaces) == 0 {
return DefaultRaceID
}
randomIndex := rand.Intn(len(r.evilRaces))
raceName := r.evilRaces[randomIndex]
if raceID, exists := r.raceMap[raceName]; exists {
return raceID
}
return DefaultRaceID // Default to Human if error
}
// IsValidRaceID checks if a race ID is valid
func (r *Races) IsValidRaceID(raceID int8) bool {
return raceID >= MinRaceID && raceID <= MaxRaceID
}
// GetAllRaces returns all race IDs and their friendly names
func (r *Races) GetAllRaces() map[int8]string {
r.mutex.RLock()
defer r.mutex.RUnlock()
result := make(map[int8]string)
for raceID, friendlyName := range r.friendlyNameMap {
result[raceID] = friendlyName
}
return result
}
// GetRacesByAlignment returns races filtered by alignment
func (r *Races) GetRacesByAlignment(alignment string) []int8 {
r.mutex.RLock()
defer r.mutex.RUnlock()
var raceNames []string
switch strings.ToLower(alignment) {
case AlignmentGood:
raceNames = r.goodRaces
case AlignmentEvil:
raceNames = r.evilRaces
default:
// Return all races for neutral or unknown alignment
result := make([]int8, 0, len(r.friendlyNameMap))
for raceID := range r.friendlyNameMap {
result = append(result, raceID)
}
return result
}
result := make([]int8, 0, len(raceNames))
for _, raceName := range raceNames {
if raceID, exists := r.raceMap[raceName]; exists {
result = append(result, raceID)
}
}
return result
}
// IsGoodRace checks if a race is considered good-aligned
func (r *Races) IsGoodRace(raceID int8) bool {
r.mutex.RLock()
defer r.mutex.RUnlock()
raceName := ""
for name, id := range r.raceMap {
if id == raceID {
raceName = name
break
}
}
if raceName == "" {
return false
}
for _, goodRace := range r.goodRaces {
if goodRace == raceName {
return true
}
}
return false
}
// IsEvilRace checks if a race is considered evil-aligned
func (r *Races) IsEvilRace(raceID int8) bool {
r.mutex.RLock()
defer r.mutex.RUnlock()
raceName := ""
for name, id := range r.raceMap {
if id == raceID {
raceName = name
break
}
}
if raceName == "" {
return false
}
for _, evilRace := range r.evilRaces {
if evilRace == raceName {
return true
}
}
return false
}
// IsNeutralRace checks if a race appears in both good and evil lists (neutral)
func (r *Races) IsNeutralRace(raceID int8) bool {
return r.IsGoodRace(raceID) && r.IsEvilRace(raceID)
}
// GetRaceAlignment returns the primary alignment of a race
func (r *Races) GetRaceAlignment(raceID int8) string {
if r.IsNeutralRace(raceID) {
return AlignmentNeutral
} else if r.IsGoodRace(raceID) {
return AlignmentGood
} else if r.IsEvilRace(raceID) {
return AlignmentEvil
}
return AlignmentNeutral // Default for invalid races
}
// GetRaceCount returns the total number of races
func (r *Races) GetRaceCount() int {
r.mutex.RLock()
defer r.mutex.RUnlock()
return len(r.friendlyNameMap)
}
// GetGoodRaceCount returns the number of good-aligned races
func (r *Races) GetGoodRaceCount() int {
r.mutex.RLock()
defer r.mutex.RUnlock()
return len(r.goodRaces)
}
// GetEvilRaceCount returns the number of evil-aligned races
func (r *Races) GetEvilRaceCount() int {
r.mutex.RLock()
defer r.mutex.RUnlock()
return len(r.evilRaces)
}
// GetRaceInfo returns comprehensive information about a race
func (r *Races) GetRaceInfo(raceID int8) map[string]any {
r.mutex.RLock()
defer r.mutex.RUnlock()
info := make(map[string]any)
if !r.IsValidRaceID(raceID) {
info["valid"] = false
return info
}
info["valid"] = true
info["race_id"] = raceID
info["name"] = r.GetRaceName(raceID)
info["display_name"] = r.GetRaceNameCase(raceID)
info["alignment"] = r.GetRaceAlignment(raceID)
info["is_good"] = r.IsGoodRace(raceID)
info["is_evil"] = r.IsEvilRace(raceID)
info["is_neutral"] = r.IsNeutralRace(raceID)
return info
}
// Global races instance
var globalRaces *Races
var initRacesOnce sync.Once
// GetGlobalRaces returns the global races manager (singleton)
func GetGlobalRaces() *Races {
initRacesOnce.Do(func() {
globalRaces = NewRaces()
})
return globalRaces
}