modernize class package

This commit is contained in:
Sky Johnson 2025-08-07 17:17:44 -05:00
parent 79ee999150
commit a47ad4f737
9 changed files with 778 additions and 3448 deletions

359
internal/classes/class.go Normal file
View File

@ -0,0 +1,359 @@
package classes
import (
"strings"
)
// classMap contains the mapping of class names to IDs
var classMap = map[string]int8{
"COMMONER": ClassCommoner,
"FIGHTER": ClassFighter,
"WARRIOR": ClassWarrior,
"GUARDIAN": ClassGuardian,
"BERSERKER": ClassBerserker,
"BRAWLER": ClassBrawler,
"MONK": ClassMonk,
"BRUISER": ClassBruiser,
"CRUSADER": ClassCrusader,
"SHADOWKNIGHT": ClassShadowknight,
"PALADIN": ClassPaladin,
"PRIEST": ClassPriest,
"CLERIC": ClassCleric,
"TEMPLAR": ClassTemplar,
"INQUISITOR": ClassInquisitor,
"DRUID": ClassDruid,
"WARDEN": ClassWarden,
"FURY": ClassFury,
"SHAMAN": ClassShaman,
"MYSTIC": ClassMystic,
"DEFILER": ClassDefiler,
"MAGE": ClassMage,
"SORCERER": ClassSorcerer,
"WIZARD": ClassWizard,
"WARLOCK": ClassWarlock,
"ENCHANTER": ClassEnchanter,
"ILLUSIONIST": ClassIllusionist,
"COERCER": ClassCoercer,
"SUMMONER": ClassSummoner,
"CONJUROR": ClassConjuror,
"NECROMANCER": ClassNecromancer,
"SCOUT": ClassScout,
"ROGUE": ClassRogue,
"SWASHBUCKLER": ClassSwashbuckler,
"BRIGAND": ClassBrigand,
"BARD": ClassBard,
"TROUBADOR": ClassTroubador,
"DIRGE": ClassDirge,
"PREDATOR": ClassPredator,
"RANGER": ClassRanger,
"ASSASSIN": ClassAssassin,
"ANIMALIST": ClassAnimalist,
"BEASTLORD": ClassBeastlord,
"SHAPER": ClassShaper,
"CHANNELER": ClassChanneler,
"ARTISAN": ClassArtisan,
"CRAFTSMAN": ClassCraftsman,
"PROVISIONER": ClassProvisioner,
"WOODWORKER": ClassWoodworker,
"CARPENTER": ClassCarpenter,
"OUTFITTER": ClassOutfitter,
"ARMORER": ClassArmorer,
"WEAPONSMITH": ClassWeaponsmith,
"TAILOR": ClassTailor,
"SCHOLAR": ClassScholar,
"JEWELER": ClassJeweler,
"SAGE": ClassSage,
"ALCHEMIST": ClassAlchemist,
}
// displayNameMap contains the display names for each class ID
var displayNameMap = map[int8]string{
ClassCommoner: "Commoner",
ClassFighter: "Fighter",
ClassWarrior: "Warrior",
ClassGuardian: "Guardian",
ClassBerserker: "Berserker",
ClassBrawler: "Brawler",
ClassMonk: "Monk",
ClassBruiser: "Bruiser",
ClassCrusader: "Crusader",
ClassShadowknight: "Shadowknight",
ClassPaladin: "Paladin",
ClassPriest: "Priest",
ClassCleric: "Cleric",
ClassTemplar: "Templar",
ClassInquisitor: "Inquisitor",
ClassDruid: "Druid",
ClassWarden: "Warden",
ClassFury: "Fury",
ClassShaman: "Shaman",
ClassMystic: "Mystic",
ClassDefiler: "Defiler",
ClassMage: "Mage",
ClassSorcerer: "Sorcerer",
ClassWizard: "Wizard",
ClassWarlock: "Warlock",
ClassEnchanter: "Enchanter",
ClassIllusionist: "Illusionist",
ClassCoercer: "Coercer",
ClassSummoner: "Summoner",
ClassConjuror: "Conjuror",
ClassNecromancer: "Necromancer",
ClassScout: "Scout",
ClassRogue: "Rogue",
ClassSwashbuckler: "Swashbuckler",
ClassBrigand: "Brigand",
ClassBard: "Bard",
ClassTroubador: "Troubador",
ClassDirge: "Dirge",
ClassPredator: "Predator",
ClassRanger: "Ranger",
ClassAssassin: "Assassin",
ClassAnimalist: "Animalist",
ClassBeastlord: "Beastlord",
ClassShaper: "Shaper",
ClassChanneler: "Channeler",
ClassArtisan: "Artisan",
ClassCraftsman: "Craftsman",
ClassProvisioner: "Provisioner",
ClassWoodworker: "Woodworker",
ClassCarpenter: "Carpenter",
ClassOutfitter: "Outfitter",
ClassArmorer: "Armorer",
ClassWeaponsmith: "Weaponsmith",
ClassTailor: "Tailor",
ClassScholar: "Scholar",
ClassJeweler: "Jeweler",
ClassSage: "Sage",
ClassAlchemist: "Alchemist",
}
// GetClassID returns the class ID for a given class name
// Converted from C++ Classes::GetClassID
func GetClassID(name string) int8 {
className := strings.ToUpper(strings.TrimSpace(name))
if classID, exists := classMap[className]; exists {
return classID
}
return -1 // Invalid class
}
// GetClassName returns the uppercase class name for a given ID
// Converted from C++ Classes::GetClassName
func GetClassName(classID int8) string {
// Search through class map to find the name
for name, id := range classMap {
if id == classID {
return name
}
}
return "" // Invalid class ID
}
// GetClassNameCase returns the friendly display name for a given class ID
// Converted from C++ Classes::GetClassNameCase
func GetClassNameCase(classID int8) string {
if displayName, exists := displayNameMap[classID]; exists {
return displayName
}
return "" // Invalid class ID
}
// GetBaseClass returns the base class ID for a given class
// Converted from C++ Classes::GetBaseClass
func GetBaseClass(classID int8) int8 {
if classID >= ClassWarrior && classID <= ClassPaladin {
return ClassFighter
}
if (classID >= ClassCleric && classID <= ClassDefiler) || (classID == ClassShaper || classID == ClassChanneler) {
return ClassPriest
}
if classID >= ClassSorcerer && classID <= ClassNecromancer {
return ClassMage
}
if classID >= ClassRogue && classID <= ClassBeastlord {
return ClassScout
}
return ClassCommoner // Default for unknown classes
}
// GetSecondaryBaseClass returns the secondary base class ID for specialized classes
// Converted from C++ Classes::GetSecondaryBaseClass
func GetSecondaryBaseClass(classID int8) int8 {
switch classID {
case ClassGuardian, ClassBerserker:
return ClassWarrior
case ClassMonk, ClassBruiser:
return ClassBrawler
case ClassShadowknight, ClassPaladin:
return ClassCrusader
case ClassTemplar, ClassInquisitor:
return ClassCleric
case ClassWarden, ClassFury:
return ClassDruid
case ClassMystic, ClassDefiler:
return ClassShaman
case ClassWizard, ClassWarlock:
return ClassSorcerer
case ClassIllusionist, ClassCoercer:
return ClassEnchanter
case ClassConjuror, ClassNecromancer:
return ClassSummoner
case ClassSwashbuckler, ClassBrigand:
return ClassRogue
case ClassTroubador, ClassDirge:
return ClassBard
case ClassRanger, ClassAssassin:
return ClassPredator
case ClassBeastlord:
return ClassAnimalist
case ClassChanneler:
return ClassShaper
}
return ClassCommoner // Default for unknown classes
}
// GetTSBaseClass returns the tradeskill base class ID
// Converted from C++ Classes::GetTSBaseClass
func GetTSBaseClass(classID int8) int8 {
// This function maps tradeskill class IDs to their base tradeskill progression levels
// The C++ code uses offset of 42 between adventure and tradeskill class systems
if classID+42 >= ClassArtisan {
return ClassArtisan - 44 // Returns 1 (base artisan level)
}
return classID // For non-tradeskill classes, return as-is
}
// GetSecondaryTSBaseClass returns the secondary tradeskill base class ID
// Converted from C++ Classes::GetSecondaryTSBaseClass
func GetSecondaryTSBaseClass(classID int8) int8 {
ret := classID + 42
if ret == ClassArtisan {
return ClassArtisan - 44
} else if ret >= ClassCraftsman && ret < ClassOutfitter {
return ClassCraftsman - 44
} else if ret >= ClassOutfitter && ret < ClassScholar {
return ClassOutfitter - 44
} else if ret >= ClassScholar {
return ClassScholar - 44
}
return classID
}
// IsValidClassID checks if a class ID is valid
func IsValidClassID(classID int8) bool {
return classID >= MinClassID && classID <= MaxClassID
}
// IsAdventureClass checks if a class is an adventure class
func IsAdventureClass(classID int8) bool {
return classID >= ClassCommoner && classID <= ClassChanneler
}
// IsTradeskillClass checks if a class is a tradeskill class
func IsTradeskillClass(classID int8) bool {
return classID >= ClassArtisan && classID <= ClassAlchemist
}
// GetClassType returns the type of class (adventure, tradeskill, etc.)
func GetClassType(classID int8) string {
if IsAdventureClass(classID) {
return ClassTypeAdventure
}
if IsTradeskillClass(classID) {
return ClassTypeTradeskill
}
return ClassTypeSpecial
}
// GetAllClasses returns all class IDs and their display names
func GetAllClasses() map[int8]string {
result := make(map[int8]string)
for classID, displayName := range displayNameMap {
result[classID] = displayName
}
return result
}
// GetClassInfo returns comprehensive information about a class
func GetClassInfo(classID int8) map[string]any {
info := make(map[string]any)
if !IsValidClassID(classID) {
info["valid"] = false
return info
}
info["valid"] = true
info["class_id"] = classID
info["name"] = GetClassName(classID)
info["display_name"] = GetClassNameCase(classID)
info["base_class"] = GetBaseClass(classID)
info["secondary_base_class"] = GetSecondaryBaseClass(classID)
info["type"] = GetClassType(classID)
info["is_adventure"] = IsAdventureClass(classID)
info["is_tradeskill"] = IsTradeskillClass(classID)
return info
}
// GetClassHierarchy returns the full class hierarchy for a given class
func GetClassHierarchy(classID int8) []int8 {
if !IsValidClassID(classID) {
return nil
}
hierarchy := []int8{classID}
// Add secondary base class if it exists
secondary := GetSecondaryBaseClass(classID)
if secondary != ClassCommoner && secondary != classID {
hierarchy = append(hierarchy, secondary)
}
// Add base class
base := GetBaseClass(classID)
if base != ClassCommoner && base != classID {
// Check if base is already in hierarchy (from secondary)
found := false
for _, id := range hierarchy {
if id == base {
found = true
break
}
}
if !found {
hierarchy = append(hierarchy, base)
}
}
// Always add Commoner as the root
if classID != ClassCommoner {
hierarchy = append(hierarchy, ClassCommoner)
}
return hierarchy
}
// IsSameArchetype checks if two classes share the same base archetype
func IsSameArchetype(classID1, classID2 int8) bool {
if !IsValidClassID(classID1) || !IsValidClassID(classID2) {
return false
}
return GetBaseClass(classID1) == GetBaseClass(classID2)
}
// GetArchetypeClasses returns all classes of a given archetype
func GetArchetypeClasses(archetypeID int8) []int8 {
var classes []int8
for classID := MinClassID; classID <= MaxClassID; classID++ {
if GetBaseClass(classID) == archetypeID {
classes = append(classes, classID)
}
}
return classes
}

View File

@ -0,0 +1,372 @@
package classes
import (
"testing"
)
func TestGetClassID(t *testing.T) {
tests := []struct {
name string
input string
expected int8
}{
{"Uppercase", "WARRIOR", ClassWarrior},
{"Lowercase", "warrior", ClassWarrior},
{"Mixed case", "WaRrIoR", ClassWarrior},
{"With spaces", " WARRIOR ", ClassWarrior},
{"Invalid", "INVALID_CLASS", -1},
{"Empty", "", -1},
{"Tradeskill", "CARPENTER", ClassCarpenter},
{"Special", "CHANNELER", ClassChanneler},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetClassID(tt.input)
if result != tt.expected {
t.Errorf("GetClassID(%q) = %d, want %d", tt.input, result, tt.expected)
}
})
}
}
func TestGetClassName(t *testing.T) {
tests := []struct {
classID int8
expected string
}{
{ClassWarrior, "WARRIOR"},
{ClassPriest, "PRIEST"},
{ClassCarpenter, "CARPENTER"},
{ClassChanneler, "CHANNELER"},
{-1, ""},
{100, ""},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := GetClassName(tt.classID)
if result != tt.expected {
t.Errorf("GetClassName(%d) = %q, want %q", tt.classID, result, tt.expected)
}
})
}
}
func TestGetClassNameCase(t *testing.T) {
tests := []struct {
classID int8
expected string
}{
{ClassWarrior, "Warrior"},
{ClassShadowknight, "Shadowknight"},
{ClassTroubador, "Troubador"},
{ClassCarpenter, "Carpenter"},
{-1, ""},
{100, ""},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := GetClassNameCase(tt.classID)
if result != tt.expected {
t.Errorf("GetClassNameCase(%d) = %q, want %q", tt.classID, result, tt.expected)
}
})
}
}
func TestGetBaseClass(t *testing.T) {
tests := []struct {
classID int8
expected int8
}{
{ClassGuardian, ClassFighter},
{ClassBerserker, ClassFighter},
{ClassTemplar, ClassPriest},
{ClassWizard, ClassMage},
{ClassRanger, ClassScout},
{ClassChanneler, ClassPriest},
{ClassCommoner, ClassCommoner},
{ClassFighter, ClassCommoner},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := GetBaseClass(tt.classID)
if result != tt.expected {
t.Errorf("GetBaseClass(%d) = %d, want %d", tt.classID, result, tt.expected)
}
})
}
}
func TestGetSecondaryBaseClass(t *testing.T) {
tests := []struct {
classID int8
expected int8
}{
{ClassGuardian, ClassWarrior},
{ClassBerserker, ClassWarrior},
{ClassMonk, ClassBrawler},
{ClassTemplar, ClassCleric},
{ClassWizard, ClassSorcerer},
{ClassRanger, ClassPredator},
{ClassBeastlord, ClassAnimalist},
{ClassChanneler, ClassShaper},
{ClassWarrior, ClassCommoner}, // No secondary
{ClassFighter, ClassCommoner}, // No secondary
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := GetSecondaryBaseClass(tt.classID)
if result != tt.expected {
t.Errorf("GetSecondaryBaseClass(%d) = %d, want %d", tt.classID, result, tt.expected)
}
})
}
}
func TestGetTSBaseClass(t *testing.T) {
tests := []struct {
classID int8
expected int8
}{
{3, 1}, // Guardian (3+42=45 >= ClassArtisan) returns ClassArtisan-44 = 1
{10, 1}, // Paladin (10+42=52 >= ClassArtisan) returns ClassArtisan-44 = 1
{0, 0}, // Commoner (0+42=42 < ClassArtisan) returns 0
{1, 1}, // Fighter (1+42=43 < ClassArtisan) returns 1
{2, 2}, // Warrior (2+42=44 < ClassArtisan) returns 2
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := GetTSBaseClass(tt.classID)
if result != tt.expected {
t.Errorf("GetTSBaseClass(%d) = %d, want %d", tt.classID, result, tt.expected)
}
})
}
}
func TestIsValidClassID(t *testing.T) {
tests := []struct {
classID int8
expected bool
}{
{ClassCommoner, true},
{ClassWarrior, true},
{ClassAlchemist, true},
{-1, false},
{100, false},
{58, false},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := IsValidClassID(tt.classID)
if result != tt.expected {
t.Errorf("IsValidClassID(%d) = %v, want %v", tt.classID, result, tt.expected)
}
})
}
}
func TestIsAdventureClass(t *testing.T) {
tests := []struct {
classID int8
expected bool
}{
{ClassCommoner, true},
{ClassWarrior, true},
{ClassChanneler, true},
{ClassArtisan, false},
{ClassCarpenter, false},
{-1, false},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := IsAdventureClass(tt.classID)
if result != tt.expected {
t.Errorf("IsAdventureClass(%d) = %v, want %v", tt.classID, result, tt.expected)
}
})
}
}
func TestIsTradeskillClass(t *testing.T) {
tests := []struct {
classID int8
expected bool
}{
{ClassArtisan, true},
{ClassCarpenter, true},
{ClassAlchemist, true},
{ClassWarrior, false},
{ClassCommoner, false},
{-1, false},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := IsTradeskillClass(tt.classID)
if result != tt.expected {
t.Errorf("IsTradeskillClass(%d) = %v, want %v", tt.classID, result, tt.expected)
}
})
}
}
func TestGetClassType(t *testing.T) {
tests := []struct {
classID int8
expected string
}{
{ClassWarrior, ClassTypeAdventure},
{ClassChanneler, ClassTypeAdventure},
{ClassArtisan, ClassTypeTradeskill},
{ClassCarpenter, ClassTypeTradeskill},
{-1, ClassTypeSpecial},
{100, ClassTypeSpecial},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := GetClassType(tt.classID)
if result != tt.expected {
t.Errorf("GetClassType(%d) = %q, want %q", tt.classID, result, tt.expected)
}
})
}
}
func TestGetAllClasses(t *testing.T) {
classes := GetAllClasses()
// Check we have the right number of classes
if len(classes) != 58 {
t.Errorf("GetAllClasses() returned %d classes, want 58", len(classes))
}
// Check a few specific classes
if name, ok := classes[ClassWarrior]; !ok || name != "Warrior" {
t.Errorf("GetAllClasses()[ClassWarrior] = %q, %v; want 'Warrior', true", name, ok)
}
if name, ok := classes[ClassCarpenter]; !ok || name != "Carpenter" {
t.Errorf("GetAllClasses()[ClassCarpenter] = %q, %v; want 'Carpenter', true", name, ok)
}
}
func TestGetClassInfo(t *testing.T) {
// Test valid class
info := GetClassInfo(ClassGuardian)
if !info["valid"].(bool) {
t.Error("GetClassInfo(ClassGuardian) should be valid")
}
if info["class_id"].(int8) != ClassGuardian {
t.Errorf("GetClassInfo(ClassGuardian).class_id = %v, want %d", info["class_id"], ClassGuardian)
}
if info["name"].(string) != "GUARDIAN" {
t.Errorf("GetClassInfo(ClassGuardian).name = %v, want 'GUARDIAN'", info["name"])
}
if info["display_name"].(string) != "Guardian" {
t.Errorf("GetClassInfo(ClassGuardian).display_name = %v, want 'Guardian'", info["display_name"])
}
if info["base_class"].(int8) != ClassFighter {
t.Errorf("GetClassInfo(ClassGuardian).base_class = %v, want %d", info["base_class"], ClassFighter)
}
if info["secondary_base_class"].(int8) != ClassWarrior {
t.Errorf("GetClassInfo(ClassGuardian).secondary_base_class = %v, want %d", info["secondary_base_class"], ClassWarrior)
}
if !info["is_adventure"].(bool) {
t.Error("GetClassInfo(ClassGuardian).is_adventure should be true")
}
if info["is_tradeskill"].(bool) {
t.Error("GetClassInfo(ClassGuardian).is_tradeskill should be false")
}
// Test invalid class
info = GetClassInfo(-1)
if info["valid"].(bool) {
t.Error("GetClassInfo(-1) should be invalid")
}
}
func TestGetClassHierarchy(t *testing.T) {
tests := []struct {
classID int8
expected []int8
}{
{ClassGuardian, []int8{ClassGuardian, ClassWarrior, ClassFighter, ClassCommoner}},
{ClassWizard, []int8{ClassWizard, ClassSorcerer, ClassMage, ClassCommoner}},
{ClassFighter, []int8{ClassFighter, ClassCommoner}},
{ClassCommoner, []int8{ClassCommoner}},
{-1, nil},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := GetClassHierarchy(tt.classID)
if len(result) != len(tt.expected) {
t.Errorf("GetClassHierarchy(%d) = %v, want %v", tt.classID, result, tt.expected)
return
}
for i, id := range result {
if id != tt.expected[i] {
t.Errorf("GetClassHierarchy(%d)[%d] = %d, want %d", tt.classID, i, id, tt.expected[i])
}
}
})
}
}
func TestIsSameArchetype(t *testing.T) {
tests := []struct {
classID1 int8
classID2 int8
expected bool
}{
{ClassGuardian, ClassBerserker, true}, // Both Fighter archetype
{ClassGuardian, ClassMonk, true}, // Both Fighter archetype
{ClassWizard, ClassWarlock, true}, // Both Mage archetype
{ClassGuardian, ClassWizard, false}, // Different archetypes
{ClassCommoner, ClassCommoner, true}, // Same class
{-1, ClassGuardian, false}, // Invalid class
{ClassGuardian, -1, false}, // Invalid class
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := IsSameArchetype(tt.classID1, tt.classID2)
if result != tt.expected {
t.Errorf("IsSameArchetype(%d, %d) = %v, want %v", tt.classID1, tt.classID2, result, tt.expected)
}
})
}
}
func TestGetArchetypeClasses(t *testing.T) {
// Test Fighter archetype
fighterClasses := GetArchetypeClasses(ClassFighter)
expectedFighterCount := 9 // Warrior through Paladin (2-10)
if len(fighterClasses) != expectedFighterCount {
t.Errorf("GetArchetypeClasses(ClassFighter) returned %d classes, want %d", len(fighterClasses), expectedFighterCount)
}
// Verify all returned classes are fighters
for _, classID := range fighterClasses {
if GetBaseClass(classID) != ClassFighter {
t.Errorf("GetArchetypeClasses(ClassFighter) returned non-fighter class %d", classID)
}
}
// Test Mage archetype
mageClasses := GetArchetypeClasses(ClassMage)
expectedMageCount := 9 // Sorcerer through Necromancer (22-30)
if len(mageClasses) != expectedMageCount {
t.Errorf("GetArchetypeClasses(ClassMage) returned %d classes, want %d", len(mageClasses), expectedMageCount)
}
}

View File

@ -1,366 +0,0 @@
package classes
import (
"strings"
"sync"
)
// Classes manages class information and lookups
// Converted from C++ Classes class
type Classes struct {
// Class name to ID mapping (uppercase keys)
classMap map[string]int8
// ID to display name mapping for friendly names
displayNameMap map[int8]string
// Thread safety
mutex sync.RWMutex
}
// NewClasses creates a new classes manager with all EQ2 classes
// Converted from C++ Classes::Classes constructor
func NewClasses() *Classes {
classes := &Classes{
classMap: make(map[string]int8),
displayNameMap: make(map[int8]string),
}
classes.initializeClasses()
return classes
}
// initializeClasses sets up all class mappings
func (c *Classes) initializeClasses() {
// Initialize class name to ID mappings (from C++ constructor)
c.classMap[ClassNameCommoner] = ClassCommoner
c.classMap[ClassNameFighter] = ClassFighter
c.classMap[ClassNameWarrior] = ClassWarrior
c.classMap[ClassNameGuardian] = ClassGuardian
c.classMap[ClassNameBerserker] = ClassBerserker
c.classMap[ClassNameBrawler] = ClassBrawler
c.classMap[ClassNameMonk] = ClassMonk
c.classMap[ClassNameBruiser] = ClassBruiser
c.classMap[ClassNameCrusader] = ClassCrusader
c.classMap[ClassNameShadowknight] = ClassShadowknight
c.classMap[ClassNamePaladin] = ClassPaladin
c.classMap[ClassNamePriest] = ClassPriest
c.classMap[ClassNameCleric] = ClassCleric
c.classMap[ClassNameTemplar] = ClassTemplar
c.classMap[ClassNameInquisitor] = ClassInquisitor
c.classMap[ClassNameDruid] = ClassDruid
c.classMap[ClassNameWarden] = ClassWarden
c.classMap[ClassNameFury] = ClassFury
c.classMap[ClassNameShaman] = ClassShaman
c.classMap[ClassNameMystic] = ClassMystic
c.classMap[ClassNameDefiler] = ClassDefiler
c.classMap[ClassNameMage] = ClassMage
c.classMap[ClassNameSorcerer] = ClassSorcerer
c.classMap[ClassNameWizard] = ClassWizard
c.classMap[ClassNameWarlock] = ClassWarlock
c.classMap[ClassNameEnchanter] = ClassEnchanter
c.classMap[ClassNameIllusionist] = ClassIllusionist
c.classMap[ClassNameCoercer] = ClassCoercer
c.classMap[ClassNameSummoner] = ClassSummoner
c.classMap[ClassNameConjuror] = ClassConjuror
c.classMap[ClassNameNecromancer] = ClassNecromancer
c.classMap[ClassNameScout] = ClassScout
c.classMap[ClassNameRogue] = ClassRogue
c.classMap[ClassNameSwashbuckler] = ClassSwashbuckler
c.classMap[ClassNameBrigand] = ClassBrigand
c.classMap[ClassNameBard] = ClassBard
c.classMap[ClassNameTroubador] = ClassTroubador
c.classMap[ClassNameDirge] = ClassDirge
c.classMap[ClassNamePredator] = ClassPredator
c.classMap[ClassNameRanger] = ClassRanger
c.classMap[ClassNameAssassin] = ClassAssassin
c.classMap[ClassNameAnimalist] = ClassAnimalist
c.classMap[ClassNameBeastlord] = ClassBeastlord
c.classMap[ClassNameShaper] = ClassShaper
c.classMap[ClassNameChanneler] = ClassChanneler
c.classMap[ClassNameArtisan] = ClassArtisan
c.classMap[ClassNameCraftsman] = ClassCraftsman
c.classMap[ClassNameProvisioner] = ClassProvisioner
c.classMap[ClassNameWoodworker] = ClassWoodworker
c.classMap[ClassNameCarpenter] = ClassCarpenter
c.classMap[ClassNameOutfitter] = ClassOutfitter
c.classMap[ClassNameArmorer] = ClassArmorer
c.classMap[ClassNameWeaponsmith] = ClassWeaponsmith
c.classMap[ClassNameTailor] = ClassTailor
c.classMap[ClassNameScholar] = ClassScholar
c.classMap[ClassNameJeweler] = ClassJeweler
c.classMap[ClassNameSage] = ClassSage
c.classMap[ClassNameAlchemist] = ClassAlchemist
// Initialize display names
c.displayNameMap[ClassCommoner] = DisplayNameCommoner
c.displayNameMap[ClassFighter] = DisplayNameFighter
c.displayNameMap[ClassWarrior] = DisplayNameWarrior
c.displayNameMap[ClassGuardian] = DisplayNameGuardian
c.displayNameMap[ClassBerserker] = DisplayNameBerserker
c.displayNameMap[ClassBrawler] = DisplayNameBrawler
c.displayNameMap[ClassMonk] = DisplayNameMonk
c.displayNameMap[ClassBruiser] = DisplayNameBruiser
c.displayNameMap[ClassCrusader] = DisplayNameCrusader
c.displayNameMap[ClassShadowknight] = DisplayNameShadowknight
c.displayNameMap[ClassPaladin] = DisplayNamePaladin
c.displayNameMap[ClassPriest] = DisplayNamePriest
c.displayNameMap[ClassCleric] = DisplayNameCleric
c.displayNameMap[ClassTemplar] = DisplayNameTemplar
c.displayNameMap[ClassInquisitor] = DisplayNameInquisitor
c.displayNameMap[ClassDruid] = DisplayNameDruid
c.displayNameMap[ClassWarden] = DisplayNameWarden
c.displayNameMap[ClassFury] = DisplayNameFury
c.displayNameMap[ClassShaman] = DisplayNameShaman
c.displayNameMap[ClassMystic] = DisplayNameMystic
c.displayNameMap[ClassDefiler] = DisplayNameDefiler
c.displayNameMap[ClassMage] = DisplayNameMage
c.displayNameMap[ClassSorcerer] = DisplayNameSorcerer
c.displayNameMap[ClassWizard] = DisplayNameWizard
c.displayNameMap[ClassWarlock] = DisplayNameWarlock
c.displayNameMap[ClassEnchanter] = DisplayNameEnchanter
c.displayNameMap[ClassIllusionist] = DisplayNameIllusionist
c.displayNameMap[ClassCoercer] = DisplayNameCoercer
c.displayNameMap[ClassSummoner] = DisplayNameSummoner
c.displayNameMap[ClassConjuror] = DisplayNameConjuror
c.displayNameMap[ClassNecromancer] = DisplayNameNecromancer
c.displayNameMap[ClassScout] = DisplayNameScout
c.displayNameMap[ClassRogue] = DisplayNameRogue
c.displayNameMap[ClassSwashbuckler] = DisplayNameSwashbuckler
c.displayNameMap[ClassBrigand] = DisplayNameBrigand
c.displayNameMap[ClassBard] = DisplayNameBard
c.displayNameMap[ClassTroubador] = DisplayNameTroubador
c.displayNameMap[ClassDirge] = DisplayNameDirge
c.displayNameMap[ClassPredator] = DisplayNamePredator
c.displayNameMap[ClassRanger] = DisplayNameRanger
c.displayNameMap[ClassAssassin] = DisplayNameAssassin
c.displayNameMap[ClassAnimalist] = DisplayNameAnimalist
c.displayNameMap[ClassBeastlord] = DisplayNameBeastlord
c.displayNameMap[ClassShaper] = DisplayNameShaper
c.displayNameMap[ClassChanneler] = DisplayNameChanneler
c.displayNameMap[ClassArtisan] = DisplayNameArtisan
c.displayNameMap[ClassCraftsman] = DisplayNameCraftsman
c.displayNameMap[ClassProvisioner] = DisplayNameProvisioner
c.displayNameMap[ClassWoodworker] = DisplayNameWoodworker
c.displayNameMap[ClassCarpenter] = DisplayNameCarpenter
c.displayNameMap[ClassOutfitter] = DisplayNameOutfitter
c.displayNameMap[ClassArmorer] = DisplayNameArmorer
c.displayNameMap[ClassWeaponsmith] = DisplayNameWeaponsmith
c.displayNameMap[ClassTailor] = DisplayNameTailor
c.displayNameMap[ClassScholar] = DisplayNameScholar
c.displayNameMap[ClassJeweler] = DisplayNameJeweler
c.displayNameMap[ClassSage] = DisplayNameSage
c.displayNameMap[ClassAlchemist] = DisplayNameAlchemist
}
// GetClassID returns the class ID for a given class name
// Converted from C++ Classes::GetClassID
func (c *Classes) GetClassID(name string) int8 {
c.mutex.RLock()
defer c.mutex.RUnlock()
className := strings.ToUpper(strings.TrimSpace(name))
if classID, exists := c.classMap[className]; exists {
return classID
}
return -1 // Invalid class
}
// GetClassName returns the uppercase class name for a given ID
// Converted from C++ Classes::GetClassName
func (c *Classes) GetClassName(classID int8) string {
c.mutex.RLock()
defer c.mutex.RUnlock()
// Search through class map to find the name
for name, id := range c.classMap {
if id == classID {
return name
}
}
return "" // Invalid class ID
}
// GetClassNameCase returns the friendly display name for a given class ID
// Converted from C++ Classes::GetClassNameCase
func (c *Classes) GetClassNameCase(classID int8) string {
c.mutex.RLock()
defer c.mutex.RUnlock()
if displayName, exists := c.displayNameMap[classID]; exists {
return displayName
}
return "" // Invalid class ID
}
// GetBaseClass returns the base class ID for a given class
// Converted from C++ Classes::GetBaseClass
func (c *Classes) GetBaseClass(classID int8) int8 {
if classID >= ClassWarrior && classID <= ClassPaladin {
return ClassFighter
}
if (classID >= ClassCleric && classID <= ClassDefiler) || (classID == ClassShaper || classID == ClassChanneler) {
return ClassPriest
}
if classID >= ClassSorcerer && classID <= ClassNecromancer {
return ClassMage
}
if classID >= ClassRogue && classID <= ClassBeastlord {
return ClassScout
}
return ClassCommoner // Default for unknown classes
}
// GetSecondaryBaseClass returns the secondary base class ID for specialized classes
// Converted from C++ Classes::GetSecondaryBaseClass
func (c *Classes) GetSecondaryBaseClass(classID int8) int8 {
switch classID {
case ClassGuardian, ClassBerserker:
return ClassWarrior
case ClassMonk, ClassBruiser:
return ClassBrawler
case ClassShadowknight, ClassPaladin:
return ClassCrusader
case ClassTemplar, ClassInquisitor:
return ClassCleric
case ClassWarden, ClassFury:
return ClassDruid
case ClassMystic, ClassDefiler:
return ClassShaman
case ClassWizard, ClassWarlock:
return ClassSorcerer
case ClassIllusionist, ClassCoercer:
return ClassEnchanter
case ClassConjuror, ClassNecromancer:
return ClassSummoner
case ClassSwashbuckler, ClassBrigand:
return ClassRogue
case ClassTroubador, ClassDirge:
return ClassBard
case ClassRanger, ClassAssassin:
return ClassPredator
case ClassBeastlord:
return ClassAnimalist
case ClassChanneler:
return ClassShaper
}
return ClassCommoner // Default for unknown classes
}
// GetTSBaseClass returns the tradeskill base class ID
// Converted from C++ Classes::GetTSBaseClass
func (c *Classes) GetTSBaseClass(classID int8) int8 {
if classID+42 >= ClassArtisan {
return ClassArtisan - 44
}
return classID
}
// GetSecondaryTSBaseClass returns the secondary tradeskill base class ID
// Converted from C++ Classes::GetSecondaryTSBaseClass
func (c *Classes) GetSecondaryTSBaseClass(classID int8) int8 {
ret := classID + 42
if ret == ClassArtisan {
return ClassArtisan - 44
} else if ret >= ClassCraftsman && ret < ClassOutfitter {
return ClassCraftsman - 44
} else if ret >= ClassOutfitter && ret < ClassScholar {
return ClassOutfitter - 44
} else if ret >= ClassScholar {
return ClassScholar - 44
}
return classID
}
// IsValidClassID checks if a class ID is valid
func (c *Classes) IsValidClassID(classID int8) bool {
return classID >= MinClassID && classID <= MaxClassID
}
// GetAllClasses returns all class IDs and their display names
func (c *Classes) GetAllClasses() map[int8]string {
c.mutex.RLock()
defer c.mutex.RUnlock()
result := make(map[int8]string)
for classID, displayName := range c.displayNameMap {
result[classID] = displayName
}
return result
}
// IsAdventureClass checks if a class is an adventure class
func (c *Classes) IsAdventureClass(classID int8) bool {
return classID >= ClassCommoner && classID <= ClassChanneler
}
// IsTradeskillClass checks if a class is a tradeskill class
func (c *Classes) IsTradeskillClass(classID int8) bool {
return classID >= ClassArtisan && classID <= ClassAlchemist
}
// GetClassType returns the type of class (adventure, tradeskill, etc.)
func (c *Classes) GetClassType(classID int8) string {
if c.IsAdventureClass(classID) {
return ClassTypeAdventure
}
if c.IsTradeskillClass(classID) {
return ClassTypeTradeskill
}
return ClassTypeSpecial
}
// GetClassCount returns the total number of classes
func (c *Classes) GetClassCount() int {
c.mutex.RLock()
defer c.mutex.RUnlock()
return len(c.displayNameMap)
}
// GetClassInfo returns comprehensive information about a class
func (c *Classes) GetClassInfo(classID int8) map[string]any {
c.mutex.RLock()
defer c.mutex.RUnlock()
info := make(map[string]any)
if !c.IsValidClassID(classID) {
info["valid"] = false
return info
}
info["valid"] = true
info["class_id"] = classID
info["name"] = c.GetClassName(classID)
info["display_name"] = c.GetClassNameCase(classID)
info["base_class"] = c.GetBaseClass(classID)
info["secondary_base_class"] = c.GetSecondaryBaseClass(classID)
info["type"] = c.GetClassType(classID)
info["is_adventure"] = c.IsAdventureClass(classID)
info["is_tradeskill"] = c.IsTradeskillClass(classID)
return info
}
// Global classes instance
var globalClasses *Classes
var initClassesOnce sync.Once
// GetGlobalClasses returns the global classes manager (singleton)
func GetGlobalClasses() *Classes {
initClassesOnce.Do(func() {
globalClasses = NewClasses()
})
return globalClasses
}

File diff suppressed because it is too large Load Diff

View File

@ -101,126 +101,3 @@ const (
ClassTypeSpecial = "special"
)
// Class name constants for lookup (uppercase keys from C++)
const (
ClassNameCommoner = "COMMONER"
ClassNameFighter = "FIGHTER"
ClassNameWarrior = "WARRIOR"
ClassNameGuardian = "GUARDIAN"
ClassNameBerserker = "BERSERKER"
ClassNameBrawler = "BRAWLER"
ClassNameMonk = "MONK"
ClassNameBruiser = "BRUISER"
ClassNameCrusader = "CRUSADER"
ClassNameShadowknight = "SHADOWKNIGHT"
ClassNamePaladin = "PALADIN"
ClassNamePriest = "PRIEST"
ClassNameCleric = "CLERIC"
ClassNameTemplar = "TEMPLAR"
ClassNameInquisitor = "INQUISITOR"
ClassNameDruid = "DRUID"
ClassNameWarden = "WARDEN"
ClassNameFury = "FURY"
ClassNameShaman = "SHAMAN"
ClassNameMystic = "MYSTIC"
ClassNameDefiler = "DEFILER"
ClassNameMage = "MAGE"
ClassNameSorcerer = "SORCERER"
ClassNameWizard = "WIZARD"
ClassNameWarlock = "WARLOCK"
ClassNameEnchanter = "ENCHANTER"
ClassNameIllusionist = "ILLUSIONIST"
ClassNameCoercer = "COERCER"
ClassNameSummoner = "SUMMONER"
ClassNameConjuror = "CONJUROR"
ClassNameNecromancer = "NECROMANCER"
ClassNameScout = "SCOUT"
ClassNameRogue = "ROGUE"
ClassNameSwashbuckler = "SWASHBUCKLER"
ClassNameBrigand = "BRIGAND"
ClassNameBard = "BARD"
ClassNameTroubador = "TROUBADOR"
ClassNameDirge = "DIRGE"
ClassNamePredator = "PREDATOR"
ClassNameRanger = "RANGER"
ClassNameAssassin = "ASSASSIN"
ClassNameAnimalist = "ANIMALIST"
ClassNameBeastlord = "BEASTLORD"
ClassNameShaper = "SHAPER"
ClassNameChanneler = "CHANNELER"
ClassNameArtisan = "ARTISAN"
ClassNameCraftsman = "CRAFTSMAN"
ClassNameProvisioner = "PROVISIONER"
ClassNameWoodworker = "WOODWORKER"
ClassNameCarpenter = "CARPENTER"
ClassNameOutfitter = "OUTFITTER"
ClassNameArmorer = "ARMORER"
ClassNameWeaponsmith = "WEAPONSMITH"
ClassNameTailor = "TAILOR"
ClassNameScholar = "SCHOLAR"
ClassNameJeweler = "JEWELER"
ClassNameSage = "SAGE"
ClassNameAlchemist = "ALCHEMIST"
)
// Class display names (proper case)
const (
DisplayNameCommoner = "Commoner"
DisplayNameFighter = "Fighter"
DisplayNameWarrior = "Warrior"
DisplayNameGuardian = "Guardian"
DisplayNameBerserker = "Berserker"
DisplayNameBrawler = "Brawler"
DisplayNameMonk = "Monk"
DisplayNameBruiser = "Bruiser"
DisplayNameCrusader = "Crusader"
DisplayNameShadowknight = "Shadowknight"
DisplayNamePaladin = "Paladin"
DisplayNamePriest = "Priest"
DisplayNameCleric = "Cleric"
DisplayNameTemplar = "Templar"
DisplayNameInquisitor = "Inquisitor"
DisplayNameDruid = "Druid"
DisplayNameWarden = "Warden"
DisplayNameFury = "Fury"
DisplayNameShaman = "Shaman"
DisplayNameMystic = "Mystic"
DisplayNameDefiler = "Defiler"
DisplayNameMage = "Mage"
DisplayNameSorcerer = "Sorcerer"
DisplayNameWizard = "Wizard"
DisplayNameWarlock = "Warlock"
DisplayNameEnchanter = "Enchanter"
DisplayNameIllusionist = "Illusionist"
DisplayNameCoercer = "Coercer"
DisplayNameSummoner = "Summoner"
DisplayNameConjuror = "Conjuror"
DisplayNameNecromancer = "Necromancer"
DisplayNameScout = "Scout"
DisplayNameRogue = "Rogue"
DisplayNameSwashbuckler = "Swashbuckler"
DisplayNameBrigand = "Brigand"
DisplayNameBard = "Bard"
DisplayNameTroubador = "Troubador"
DisplayNameDirge = "Dirge"
DisplayNamePredator = "Predator"
DisplayNameRanger = "Ranger"
DisplayNameAssassin = "Assassin"
DisplayNameAnimalist = "Animalist"
DisplayNameBeastlord = "Beastlord"
DisplayNameShaper = "Shaper"
DisplayNameChanneler = "Channeler"
DisplayNameArtisan = "Artisan"
DisplayNameCraftsman = "Craftsman"
DisplayNameProvisioner = "Provisioner"
DisplayNameWoodworker = "Woodworker"
DisplayNameCarpenter = "Carpenter"
DisplayNameOutfitter = "Outfitter"
DisplayNameArmorer = "Armorer"
DisplayNameWeaponsmith = "Weaponsmith"
DisplayNameTailor = "Tailor"
DisplayNameScholar = "Scholar"
DisplayNameJeweler = "Jeweler"
DisplayNameSage = "Sage"
DisplayNameAlchemist = "Alchemist"
)

47
internal/classes/doc.go Normal file
View File

@ -0,0 +1,47 @@
// Package classes provides EverQuest II class definitions and lookup functions.
//
// This package manages all adventure and tradeskill class information including
// class IDs, names, hierarchies, and relationships. It provides static lookups
// for class data without requiring database access.
//
// Basic Usage:
//
// // Get class ID from name
// classID := classes.GetClassID("WARRIOR")
//
// // Get class display name
// name := classes.GetClassNameCase(classes.ClassWarrior)
//
// // Check class hierarchy
// baseClass := classes.GetBaseClass(classes.ClassGuardian) // Returns ClassFighter
// secondary := classes.GetSecondaryBaseClass(classes.ClassGuardian) // Returns ClassWarrior
//
// Class Hierarchy:
//
// Fighter -> Warrior -> Guardian/Berserker
// -> Brawler -> Monk/Bruiser
// -> Crusader -> Shadowknight/Paladin
//
// Priest -> Cleric -> Templar/Inquisitor
// -> Druid -> Warden/Fury
// -> Shaman -> Mystic/Defiler
//
// Mage -> Sorcerer -> Wizard/Warlock
// -> Enchanter -> Illusionist/Coercer
// -> Summoner -> Conjuror/Necromancer
//
// Scout -> Rogue -> Swashbuckler/Brigand
// -> Bard -> Troubador/Dirge
// -> Predator -> Ranger/Assassin
// -> Animalist -> Beastlord
//
// Tradeskill Classes:
//
// Artisan -> Craftsman -> Provisioner
// -> Woodworker -> Carpenter
// -> Outfitter -> Armorer/Weaponsmith/Tailor
// -> Scholar -> Jeweler/Sage/Alchemist
//
// The package includes all 58 class definitions from EverQuest II including
// adventure classes (0-44) and tradeskill classes (45-57).
package classes

View File

@ -1,352 +0,0 @@
package classes
import (
"fmt"
)
// ClassAware interface for entities that have class information
type ClassAware interface {
GetClass() int8
SetClass(int8)
}
// EntityWithClass interface extends ClassAware with additional entity properties
type EntityWithClass interface {
ClassAware
GetID() int32
GetName() string
GetLevel() int8
}
// ClassIntegration provides class-related functionality for other systems
type ClassIntegration struct {
classes *Classes
utils *ClassUtils
}
// NewClassIntegration creates a new class integration helper
func NewClassIntegration() *ClassIntegration {
return &ClassIntegration{
classes: GetGlobalClasses(),
utils: NewClassUtils(),
}
}
// ValidateEntityClass validates an entity's class and provides detailed information
func (ci *ClassIntegration) ValidateEntityClass(entity ClassAware) (bool, string, map[string]any) {
classID := entity.GetClass()
if !ci.classes.IsValidClassID(classID) {
return false, fmt.Sprintf("Invalid class ID: %d", classID), nil
}
classInfo := ci.classes.GetClassInfo(classID)
return true, "Valid class", classInfo
}
// GetEntityClassInfo returns comprehensive class information for an entity
func (ci *ClassIntegration) GetEntityClassInfo(entity EntityWithClass) map[string]any {
info := make(map[string]any)
// Basic entity info
info["entity_id"] = entity.GetID()
info["entity_name"] = entity.GetName()
info["entity_level"] = entity.GetLevel()
// Class information
classID := entity.GetClass()
classInfo := ci.classes.GetClassInfo(classID)
info["class"] = classInfo
// Additional class-specific info
info["description"] = ci.utils.GetClassDescription(classID)
info["eq_class_name"] = ci.utils.GetEQClassName(classID, entity.GetLevel())
info["progression"] = ci.utils.GetClassProgression(classID)
info["aliases"] = ci.utils.GetClassAliases(classID)
info["is_base_class"] = ci.utils.IsBaseClass(classID)
info["is_secondary_base"] = ci.utils.IsSecondaryBaseClass(classID)
return info
}
// ChangeEntityClass changes an entity's class with validation
func (ci *ClassIntegration) ChangeEntityClass(entity ClassAware, newClassID int8) error {
if !ci.classes.IsValidClassID(newClassID) {
return fmt.Errorf("invalid class ID: %d", newClassID)
}
oldClassID := entity.GetClass()
// Validate the class transition
if valid, reason := ci.utils.ValidateClassTransition(oldClassID, newClassID); !valid {
return fmt.Errorf("class change not allowed: %s", reason)
}
// Perform the class change
entity.SetClass(newClassID)
return nil
}
// GetRandomClassForEntity returns a random class appropriate for an entity
func (ci *ClassIntegration) GetRandomClassForEntity(classType string) int8 {
return ci.utils.GetRandomClassByType(classType)
}
// CheckClassCompatibility checks if two entities' classes are compatible for grouping
func (ci *ClassIntegration) CheckClassCompatibility(entity1, entity2 ClassAware) bool {
class1 := entity1.GetClass()
class2 := entity2.GetClass()
if !ci.classes.IsValidClassID(class1) || !ci.classes.IsValidClassID(class2) {
return false
}
// Same class is always compatible
if class1 == class2 {
return true
}
// Check if they share the same base class (good for grouping)
// base1 := ci.classes.GetBaseClass(class1)
// base2 := ci.classes.GetBaseClass(class2)
// Different base classes can group together (provides diversity)
// Same base class provides synergy
return true // For now, all classes are compatible for grouping
}
// FormatEntityClass returns a formatted class name for an entity
func (ci *ClassIntegration) FormatEntityClass(entity EntityWithClass, format string) string {
classID := entity.GetClass()
level := entity.GetLevel()
switch format {
case "eq":
return ci.utils.GetEQClassName(classID, level)
default:
return ci.utils.FormatClassName(classID, format)
}
}
// GetEntityBaseClass returns an entity's base class
func (ci *ClassIntegration) GetEntityBaseClass(entity ClassAware) int8 {
classID := entity.GetClass()
return ci.classes.GetBaseClass(classID)
}
// GetEntitySecondaryBaseClass returns an entity's secondary base class
func (ci *ClassIntegration) GetEntitySecondaryBaseClass(entity ClassAware) int8 {
classID := entity.GetClass()
return ci.classes.GetSecondaryBaseClass(classID)
}
// IsEntityAdventureClass checks if an entity has an adventure class
func (ci *ClassIntegration) IsEntityAdventureClass(entity ClassAware) bool {
classID := entity.GetClass()
return ci.classes.IsAdventureClass(classID)
}
// IsEntityTradeskillClass checks if an entity has a tradeskill class
func (ci *ClassIntegration) IsEntityTradeskillClass(entity ClassAware) bool {
classID := entity.GetClass()
return ci.classes.IsTradeskillClass(classID)
}
// GetEntitiesByClass filters entities by class
func (ci *ClassIntegration) GetEntitiesByClass(entities []ClassAware, classID int8) []ClassAware {
result := make([]ClassAware, 0)
for _, entity := range entities {
if entity.GetClass() == classID {
result = append(result, entity)
}
}
return result
}
// GetEntitiesByBaseClass filters entities by base class
func (ci *ClassIntegration) GetEntitiesByBaseClass(entities []ClassAware, baseClassID int8) []ClassAware {
result := make([]ClassAware, 0)
for _, entity := range entities {
if ci.GetEntityBaseClass(entity) == baseClassID {
result = append(result, entity)
}
}
return result
}
// GetEntitiesByClassType filters entities by class type (adventure/tradeskill)
func (ci *ClassIntegration) GetEntitiesByClassType(entities []ClassAware, classType string) []ClassAware {
result := make([]ClassAware, 0)
for _, entity := range entities {
classID := entity.GetClass()
if ci.classes.GetClassType(classID) == classType {
result = append(result, entity)
}
}
return result
}
// ValidateClassForRace checks if a class/race combination is valid
func (ci *ClassIntegration) ValidateClassForRace(classID, raceID int8) (bool, string) {
if !ci.classes.IsValidClassID(classID) {
return false, "Invalid class"
}
// Use the utility function (which currently allows all combinations)
if ci.utils.ValidateClassForRace(classID, raceID) {
return true, ""
}
className := ci.classes.GetClassNameCase(classID)
return false, fmt.Sprintf("Class %s cannot be race %d", className, raceID)
}
// GetClassStartingStats returns the starting stats for a class
func (ci *ClassIntegration) GetClassStartingStats(classID int8) map[string]int16 {
// Base stats that all classes start with
baseStats := map[string]int16{
"strength": 50,
"stamina": 50,
"agility": 50,
"wisdom": 50,
"intelligence": 50,
}
// Apply class modifiers based on class type and role
switch ci.classes.GetBaseClass(classID) {
case ClassFighter:
baseStats["strength"] += 5
baseStats["stamina"] += 5
baseStats["intelligence"] -= 3
case ClassPriest:
baseStats["wisdom"] += 5
baseStats["intelligence"] += 3
baseStats["strength"] -= 2
case ClassMage:
baseStats["intelligence"] += 5
baseStats["wisdom"] += 3
baseStats["strength"] -= 3
baseStats["stamina"] -= 2
case ClassScout:
baseStats["agility"] += 5
baseStats["stamina"] += 3
baseStats["wisdom"] -= 2
}
// Fine-tune for specific secondary base classes
switch ci.classes.GetSecondaryBaseClass(classID) {
case ClassWarrior:
baseStats["strength"] += 2
baseStats["stamina"] += 2
case ClassBrawler:
baseStats["agility"] += 2
baseStats["strength"] += 1
case ClassCrusader:
baseStats["wisdom"] += 2
baseStats["strength"] += 1
case ClassCleric:
baseStats["wisdom"] += 3
case ClassDruid:
baseStats["wisdom"] += 2
baseStats["intelligence"] += 1
case ClassShaman:
baseStats["wisdom"] += 2
baseStats["stamina"] += 1
case ClassSorcerer:
baseStats["intelligence"] += 3
case ClassEnchanter:
baseStats["intelligence"] += 2
baseStats["agility"] += 1
case ClassSummoner:
baseStats["intelligence"] += 2
baseStats["wisdom"] += 1
case ClassRogue:
baseStats["agility"] += 3
case ClassBard:
baseStats["agility"] += 2
baseStats["intelligence"] += 1
case ClassPredator:
baseStats["agility"] += 2
baseStats["stamina"] += 1
}
return baseStats
}
// CreateClassSpecificEntity creates entity data with class-specific properties
func (ci *ClassIntegration) CreateClassSpecificEntity(classID int8) map[string]any {
if !ci.classes.IsValidClassID(classID) {
return nil
}
entityData := make(map[string]any)
// Basic class info
entityData["class_id"] = classID
entityData["class_name"] = ci.classes.GetClassNameCase(classID)
entityData["class_type"] = ci.classes.GetClassType(classID)
// Starting stats
entityData["starting_stats"] = ci.GetClassStartingStats(classID)
// Class progression
entityData["progression"] = ci.utils.GetClassProgression(classID)
// Class description
entityData["description"] = ci.utils.GetClassDescription(classID)
// Role information
entityData["base_class"] = ci.classes.GetBaseClass(classID)
entityData["secondary_base_class"] = ci.classes.GetSecondaryBaseClass(classID)
return entityData
}
// GetClassSelectionData returns data for class selection UI
func (ci *ClassIntegration) GetClassSelectionData() map[string]any {
data := make(map[string]any)
// All available adventure classes (exclude tradeskill for character creation)
allClasses := ci.classes.GetAllClasses()
adventureClasses := make([]map[string]any, 0)
for classID, displayName := range allClasses {
if ci.classes.IsAdventureClass(classID) {
classData := map[string]any{
"id": classID,
"name": displayName,
"type": ci.classes.GetClassType(classID),
"description": ci.utils.GetClassDescription(classID),
"base_class": ci.classes.GetBaseClass(classID),
"secondary_base_class": ci.classes.GetSecondaryBaseClass(classID),
"starting_stats": ci.GetClassStartingStats(classID),
"progression": ci.utils.GetClassProgression(classID),
"is_base_class": ci.utils.IsBaseClass(classID),
}
adventureClasses = append(adventureClasses, classData)
}
}
data["adventure_classes"] = adventureClasses
data["statistics"] = ci.utils.GetClassStatistics()
return data
}
// Global class integration instance
var globalClassIntegration *ClassIntegration
// GetGlobalClassIntegration returns the global class integration helper
func GetGlobalClassIntegration() *ClassIntegration {
if globalClassIntegration == nil {
globalClassIntegration = NewClassIntegration()
}
return globalClassIntegration
}

View File

@ -1,455 +0,0 @@
package classes
import (
"fmt"
"sync"
)
// ClassManager provides high-level class management functionality
type ClassManager struct {
classes *Classes
utils *ClassUtils
integration *ClassIntegration
// Statistics tracking
classUsageStats map[int8]int32 // Track how often each class is used
// Thread safety
mutex sync.RWMutex
}
// NewClassManager creates a new class manager
func NewClassManager() *ClassManager {
return &ClassManager{
classes: GetGlobalClasses(),
utils: NewClassUtils(),
integration: NewClassIntegration(),
classUsageStats: make(map[int8]int32),
}
}
// RegisterClassUsage tracks class usage for statistics
func (cm *ClassManager) RegisterClassUsage(classID int8) {
if !cm.classes.IsValidClassID(classID) {
return
}
cm.mutex.Lock()
defer cm.mutex.Unlock()
cm.classUsageStats[classID]++
}
// GetClassUsageStats returns class usage statistics
func (cm *ClassManager) GetClassUsageStats() map[int8]int32 {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
// Return a copy to prevent external modification
stats := make(map[int8]int32)
for classID, count := range cm.classUsageStats {
stats[classID] = count
}
return stats
}
// GetMostPopularClass returns the most frequently used class
func (cm *ClassManager) GetMostPopularClass() (int8, int32) {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
var mostPopularClass int8 = -1
var maxUsage int32 = 0
for classID, usage := range cm.classUsageStats {
if usage > maxUsage {
maxUsage = usage
mostPopularClass = classID
}
}
return mostPopularClass, maxUsage
}
// GetLeastPopularClass returns the least frequently used class
func (cm *ClassManager) GetLeastPopularClass() (int8, int32) {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
var leastPopularClass int8 = -1
var minUsage int32 = -1
for classID, usage := range cm.classUsageStats {
if minUsage == -1 || usage < minUsage {
minUsage = usage
leastPopularClass = classID
}
}
return leastPopularClass, minUsage
}
// ResetUsageStats clears all usage statistics
func (cm *ClassManager) ResetUsageStats() {
cm.mutex.Lock()
defer cm.mutex.Unlock()
cm.classUsageStats = make(map[int8]int32)
}
// ProcessClassCommand handles class-related commands
func (cm *ClassManager) ProcessClassCommand(command string, args []string) (string, error) {
switch command {
case "list":
return cm.handleListCommand(args)
case "info":
return cm.handleInfoCommand(args)
case "random":
return cm.handleRandomCommand(args)
case "stats":
return cm.handleStatsCommand(args)
case "search":
return cm.handleSearchCommand(args)
case "progression":
return cm.handleProgressionCommand(args)
default:
return "", fmt.Errorf("unknown class command: %s", command)
}
}
// handleListCommand lists classes by criteria
func (cm *ClassManager) handleListCommand(args []string) (string, error) {
if len(args) == 0 {
// List all classes
allClasses := cm.classes.GetAllClasses()
result := "All Classes:\n"
for classID, displayName := range allClasses {
classType := cm.classes.GetClassType(classID)
baseClass := cm.classes.GetBaseClass(classID)
baseClassName := cm.classes.GetClassNameCase(baseClass)
result += fmt.Sprintf("%d: %s (%s, Base: %s)\n", classID, displayName, classType, baseClassName)
}
return result, nil
}
// List classes by type
classType := args[0]
allClasses := cm.classes.GetAllClasses()
result := fmt.Sprintf("%s Classes:\n", classType)
count := 0
for classID, displayName := range allClasses {
if cm.classes.GetClassType(classID) == classType {
baseClass := cm.classes.GetBaseClass(classID)
baseClassName := cm.classes.GetClassNameCase(baseClass)
result += fmt.Sprintf("%d: %s (Base: %s)\n", classID, displayName, baseClassName)
count++
}
}
if count == 0 {
return fmt.Sprintf("No classes found for type: %s", classType), nil
}
return result, nil
}
// handleInfoCommand provides detailed information about a class
func (cm *ClassManager) handleInfoCommand(args []string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("class name or ID required")
}
// Try to parse as class name or ID
classID := cm.utils.ParseClassName(args[0])
if classID == -1 {
return fmt.Sprintf("Invalid class: %s", args[0]), nil
}
classInfo := cm.classes.GetClassInfo(classID)
if !classInfo["valid"].(bool) {
return fmt.Sprintf("Invalid class ID: %d", classID), nil
}
result := "Class Information:\n"
result += fmt.Sprintf("ID: %d\n", classID)
result += fmt.Sprintf("Name: %s\n", classInfo["display_name"])
result += fmt.Sprintf("Type: %s\n", classInfo["type"])
result += fmt.Sprintf("Base Class: %s\n", cm.classes.GetClassNameCase(classInfo["base_class"].(int8)))
if secondaryBase := classInfo["secondary_base_class"].(int8); secondaryBase != DefaultClassID {
result += fmt.Sprintf("Secondary Base: %s\n", cm.classes.GetClassNameCase(secondaryBase))
}
result += fmt.Sprintf("Description: %s\n", cm.utils.GetClassDescription(classID))
// Add progression path
progression := cm.utils.GetClassProgression(classID)
if len(progression) > 1 {
result += "Progression Path: "
progressionNames := make([]string, len(progression))
for i, progClassID := range progression {
progressionNames[i] = cm.classes.GetClassNameCase(progClassID)
}
result += fmt.Sprintf("%s\n", cm.utils.FormatClassList(progression, " → "))
}
// Add starting stats
startingStats := cm.integration.GetClassStartingStats(classID)
if len(startingStats) > 0 {
result += "Starting Stats:\n"
for stat, value := range startingStats {
result += fmt.Sprintf(" %s: %d\n", stat, value)
}
}
// Add usage statistics if available
cm.mutex.RLock()
usage, hasUsage := cm.classUsageStats[classID]
cm.mutex.RUnlock()
if hasUsage {
result += fmt.Sprintf("Usage Count: %d\n", usage)
}
return result, nil
}
// handleRandomCommand generates random classes
func (cm *ClassManager) handleRandomCommand(args []string) (string, error) {
classType := ClassTypeAdventure
if len(args) > 0 {
classType = args[0]
}
classID := cm.utils.GetRandomClassByType(classType)
if classID == -1 {
return "Failed to generate random class", nil
}
displayName := cm.classes.GetClassNameCase(classID)
actualType := cm.classes.GetClassType(classID)
return fmt.Sprintf("Random %s Class: %s (ID: %d)", actualType, displayName, classID), nil
}
// handleStatsCommand shows class system statistics
func (cm *ClassManager) handleStatsCommand(args []string) (string, error) {
systemStats := cm.utils.GetClassStatistics()
usageStats := cm.GetClassUsageStats()
result := "Class System Statistics:\n"
result += fmt.Sprintf("Total Classes: %d\n", systemStats["total_classes"])
result += fmt.Sprintf("Adventure Classes: %d\n", systemStats["adventure_classes"])
result += fmt.Sprintf("Tradeskill Classes: %d\n", systemStats["tradeskill_classes"])
result += fmt.Sprintf("Special Classes: %d\n", systemStats["special_classes"])
if len(usageStats) > 0 {
result += "\nUsage Statistics:\n"
mostPopular, maxUsage := cm.GetMostPopularClass()
leastPopular, minUsage := cm.GetLeastPopularClass()
if mostPopular != -1 {
mostPopularName := cm.classes.GetClassNameCase(mostPopular)
result += fmt.Sprintf("Most Popular: %s (%d uses)\n", mostPopularName, maxUsage)
}
if leastPopular != -1 {
leastPopularName := cm.classes.GetClassNameCase(leastPopular)
result += fmt.Sprintf("Least Popular: %s (%d uses)\n", leastPopularName, minUsage)
}
}
// Show base class distribution
if baseDistribution, exists := systemStats["base_class_distribution"]; exists {
result += "\nBase Class Distribution:\n"
distribution := baseDistribution.(map[string][]string)
for baseClass, subClasses := range distribution {
result += fmt.Sprintf("%s: %d subclasses\n", baseClass, len(subClasses))
}
}
return result, nil
}
// handleSearchCommand searches for classes by pattern
func (cm *ClassManager) handleSearchCommand(args []string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("search pattern required")
}
pattern := args[0]
matchingClasses := cm.utils.GetClassesByPattern(pattern)
if len(matchingClasses) == 0 {
return fmt.Sprintf("No classes found matching pattern: %s", pattern), nil
}
result := fmt.Sprintf("Classes matching '%s':\n", pattern)
for _, classID := range matchingClasses {
displayName := cm.classes.GetClassNameCase(classID)
classType := cm.classes.GetClassType(classID)
baseClass := cm.classes.GetBaseClass(classID)
baseClassName := cm.classes.GetClassNameCase(baseClass)
result += fmt.Sprintf("%d: %s (%s, Base: %s)\n", classID, displayName, classType, baseClassName)
}
return result, nil
}
// handleProgressionCommand shows class progression information
func (cm *ClassManager) handleProgressionCommand(args []string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("class name or ID required")
}
classID := cm.utils.ParseClassName(args[0])
if classID == -1 {
return fmt.Sprintf("Invalid class: %s", args[0]), nil
}
progression := cm.utils.GetClassProgression(classID)
if len(progression) <= 1 {
return fmt.Sprintf("Class %s has no progression path", cm.classes.GetClassNameCase(classID)), nil
}
result := fmt.Sprintf("Progression Path for %s:\n", cm.classes.GetClassNameCase(classID))
for i, stepClassID := range progression {
stepName := cm.classes.GetClassNameCase(stepClassID)
if i == 0 {
result += fmt.Sprintf("1. %s (Starting Class)\n", stepName)
} else if i == len(progression)-1 {
result += fmt.Sprintf("%d. %s (Final Class)\n", i+1, stepName)
} else {
result += fmt.Sprintf("%d. %s\n", i+1, stepName)
}
}
return result, nil
}
// ValidateEntityClasses validates classes for a collection of entities
func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]any {
validationResults := make(map[string]any)
validCount := 0
invalidCount := 0
classDistribution := make(map[int8]int)
for i, entity := range entities {
classID := entity.GetClass()
isValid := cm.classes.IsValidClassID(classID)
if isValid {
validCount++
classDistribution[classID]++
} else {
invalidCount++
}
// Track invalid entities
if !isValid {
if validationResults["invalid_entities"] == nil {
validationResults["invalid_entities"] = make([]map[string]any, 0)
}
invalidList := validationResults["invalid_entities"].([]map[string]any)
invalidList = append(invalidList, map[string]any{
"index": i,
"class_id": classID,
})
validationResults["invalid_entities"] = invalidList
}
}
validationResults["total_entities"] = len(entities)
validationResults["valid_count"] = validCount
validationResults["invalid_count"] = invalidCount
validationResults["class_distribution"] = classDistribution
return validationResults
}
// GetClassRecommendations returns class recommendations for character creation
func (cm *ClassManager) GetClassRecommendations(preferences map[string]any) []int8 {
recommendations := make([]int8, 0)
// Check for class type preference
if classType, exists := preferences["class_type"]; exists {
if typeStr, ok := classType.(string); ok {
allClasses := cm.classes.GetAllClasses()
for classID := range allClasses {
if cm.classes.GetClassType(classID) == typeStr {
recommendations = append(recommendations, classID)
}
}
}
}
// Check for base class preference
if baseClass, exists := preferences["base_class"]; exists {
if baseClassID, ok := baseClass.(int8); ok {
subClasses := cm.utils.GetClassesForBaseClass(baseClassID)
recommendations = append(recommendations, subClasses...)
}
}
// Check for specific stat preferences
if preferredStats, exists := preferences["preferred_stats"]; exists {
if stats, ok := preferredStats.([]string); ok {
allClasses := cm.classes.GetAllClasses()
for classID := range allClasses {
startingStats := cm.integration.GetClassStartingStats(classID)
// Check if this class has bonuses in preferred stats
hasPreferredBonus := false
for _, preferredStat := range stats {
if statValue, exists := startingStats[preferredStat]; exists && statValue > 52 { // Above base of 50 + minor bonus
hasPreferredBonus = true
break
}
}
if hasPreferredBonus {
recommendations = append(recommendations, classID)
}
}
}
}
// If no specific preferences, recommend popular classes
if len(recommendations) == 0 {
// Get usage stats and recommend most popular classes
usageStats := cm.GetClassUsageStats()
if len(usageStats) > 0 {
// Sort by usage and take top classes
// For simplicity, just return all classes with usage > 0
for classID, usage := range usageStats {
if usage > 0 {
recommendations = append(recommendations, classID)
}
}
}
// If still no recommendations, return a default set of beginner-friendly classes
if len(recommendations) == 0 {
recommendations = []int8{ClassWarrior, ClassCleric, ClassWizard, ClassRogue}
}
}
return recommendations
}
// Global class manager instance
var globalClassManager *ClassManager
var initClassManagerOnce sync.Once
// GetGlobalClassManager returns the global class manager (singleton)
func GetGlobalClassManager() *ClassManager {
initClassManagerOnce.Do(func() {
globalClassManager = NewClassManager()
})
return globalClassManager
}

View File

@ -1,450 +0,0 @@
package classes
import (
"math/rand"
"strings"
)
// ClassUtils provides utility functions for class operations
type ClassUtils struct {
classes *Classes
}
// NewClassUtils creates a new class utilities instance
func NewClassUtils() *ClassUtils {
return &ClassUtils{
classes: GetGlobalClasses(),
}
}
// ParseClassName attempts to parse a class name from various input formats
func (cu *ClassUtils) ParseClassName(input string) int8 {
if input == "" {
return -1
}
// Try direct lookup first
classID := cu.classes.GetClassID(input)
if classID != -1 {
return classID
}
// Try with common variations
variations := []string{
strings.ToUpper(input),
strings.ReplaceAll(strings.ToUpper(input), " ", ""),
strings.ReplaceAll(strings.ToUpper(input), "_", ""),
strings.ReplaceAll(strings.ToUpper(input), "-", ""),
}
for _, variation := range variations {
if classID := cu.classes.GetClassID(variation); classID != -1 {
return classID
}
}
// Try matching against friendly names (case insensitive)
inputLower := strings.ToLower(input)
allClasses := cu.classes.GetAllClasses()
for classID, displayName := range allClasses {
if strings.ToLower(displayName) == inputLower {
return classID
}
}
return -1 // Not found
}
// FormatClassName returns a properly formatted class name
func (cu *ClassUtils) FormatClassName(classID int8, format string) string {
switch strings.ToLower(format) {
case "display", "friendly", "proper":
return cu.classes.GetClassNameCase(classID)
case "upper", "uppercase":
return cu.classes.GetClassName(classID)
case "lower", "lowercase":
return strings.ToLower(cu.classes.GetClassName(classID))
default:
return cu.classes.GetClassNameCase(classID) // Default to friendly name
}
}
// GetRandomClassByType returns a random class of the specified type
func (cu *ClassUtils) GetRandomClassByType(classType string) int8 {
allClasses := cu.classes.GetAllClasses()
validClasses := make([]int8, 0)
for classID := range allClasses {
if cu.classes.GetClassType(classID) == classType {
validClasses = append(validClasses, classID)
}
}
if len(validClasses) == 0 {
return DefaultClassID
}
return validClasses[rand.Intn(len(validClasses))]
}
// GetRandomAdventureClass returns a random adventure class
func (cu *ClassUtils) GetRandomAdventureClass() int8 {
return cu.GetRandomClassByType(ClassTypeAdventure)
}
// GetRandomTradeskillClass returns a random tradeskill class
func (cu *ClassUtils) GetRandomTradeskillClass() int8 {
return cu.GetRandomClassByType(ClassTypeTradeskill)
}
// ValidateClassForRace checks if a class is valid for a specific race
// This is a placeholder for future race-class restrictions
func (cu *ClassUtils) ValidateClassForRace(classID, raceID int8) bool {
// TODO: Implement race-class restrictions when race system is available
// For now, all classes can be all races
return cu.classes.IsValidClassID(classID)
}
// GetClassDescription returns a description of the class
func (cu *ClassUtils) GetClassDescription(classID int8) string {
// This would typically come from a database or configuration
// For now, provide basic descriptions based on class
switch classID {
case ClassCommoner:
return "A starting class for all characters before choosing their path."
case ClassFighter:
return "Warriors who excel in melee combat and defense."
case ClassWarrior:
return "Masters of weapons and armor, the ultimate melee combatants."
case ClassGuardian:
return "Defensive warriors who protect their allies with shield and sword."
case ClassBerserker:
return "Rage-fueled fighters who sacrifice defense for devastating attacks."
case ClassBrawler:
return "Hand-to-hand combat specialists who fight with fists and focus."
case ClassMonk:
return "Disciplined fighters who use martial arts and inner peace."
case ClassBruiser:
return "Brutal brawlers who overwhelm enemies with raw power."
case ClassCrusader:
return "Holy warriors who blend combat prowess with divine magic."
case ClassShadowknight:
return "Dark knights who wield unholy magic alongside martial skill."
case ClassPaladin:
return "Champions of good who protect the innocent with sword and spell."
case ClassPriest:
return "Divine casters who channel the power of the gods."
case ClassCleric:
return "Healers and supporters who keep their allies alive and fighting."
case ClassTemplar:
return "Protective priests who shield allies from harm."
case ClassInquisitor:
return "Militant clerics who combine healing with righteous fury."
case ClassDruid:
return "Nature priests who harness the power of the natural world."
case ClassWarden:
return "Protective druids who shield allies with nature's blessing."
case ClassFury:
return "Destructive druids who unleash nature's wrath upon enemies."
case ClassShaman:
return "Spirit-workers who commune with ancestors and totems."
case ClassMystic:
return "Supportive shamans who provide wards and spiritual guidance."
case ClassDefiler:
return "Dark shamans who corrupt and weaken their enemies."
case ClassMage:
return "Wielders of arcane magic who bend reality to their will."
case ClassSorcerer:
return "Destructive mages who specialize in damaging spells."
case ClassWizard:
return "Scholarly sorcerers who master the elements."
case ClassWarlock:
return "Dark sorcerers who deal in forbidden magic."
case ClassEnchanter:
return "Mind-controlling mages who manipulate enemies and allies."
case ClassIllusionist:
return "Deceptive enchanters who confuse and misdirect."
case ClassCoercer:
return "Dominating enchanters who force enemies to obey."
case ClassSummoner:
return "Mages who call forth creatures to fight for them."
case ClassConjuror:
return "Elemental summoners who command earth and air."
case ClassNecromancer:
return "Death mages who raise undead minions and drain life."
case ClassScout:
return "Agile fighters who rely on speed and cunning."
case ClassRogue:
return "Stealthy combatants who strike from the shadows."
case ClassSwashbuckler:
return "Dashing rogues who fight with finesse and flair."
case ClassBrigand:
return "Brutal rogues who prefer dirty fighting tactics."
case ClassBard:
return "Musical combatants who inspire allies and demoralize foes."
case ClassTroubador:
return "Supportive bards who strengthen their allies."
case ClassDirge:
return "Dark bards who weaken enemies with haunting melodies."
case ClassPredator:
return "Hunters who excel at tracking and ranged combat."
case ClassRanger:
return "Nature-loving predators who protect the wilderness."
case ClassAssassin:
return "Deadly predators who eliminate targets with precision."
case ClassAnimalist:
return "Beast masters who fight alongside animal companions."
case ClassBeastlord:
return "Animalists who have formed powerful bonds with their pets."
case ClassShaper:
return "Mystic priests who manipulate spiritual energy."
case ClassChanneler:
return "Shapers who focus spiritual power through channeling."
case ClassArtisan:
return "Crafters who create useful items for adventurers."
case ClassCraftsman:
return "Specialized artisans who work with physical materials."
case ClassProvisioner:
return "Food and drink specialists who create consumables."
case ClassWoodworker:
return "Crafters who work with wood to create furniture and tools."
case ClassCarpenter:
return "Master woodworkers who create complex wooden items."
case ClassOutfitter:
return "Equipment crafters who create armor and weapons."
case ClassArmorer:
return "Specialists in creating protective armor."
case ClassWeaponsmith:
return "Masters of weapon crafting and enhancement."
case ClassTailor:
return "Cloth workers who create clothing and soft armor."
case ClassScholar:
return "Academic crafters who create magical and scholarly items."
case ClassJeweler:
return "Specialists in creating jewelry and accessories."
case ClassSage:
return "Book and scroll crafters who preserve knowledge."
case ClassAlchemist:
return "Potion makers who brew magical elixirs and potions."
default:
return "An unknown class with mysterious abilities."
}
}
// GetClassProgression returns the class progression path
func (cu *ClassUtils) GetClassProgression(classID int8) []int8 {
progression := make([]int8, 0)
// Always start with Commoner (except for Commoner itself)
if classID != ClassCommoner {
progression = append(progression, ClassCommoner)
}
// Add base class if different from current
baseClass := cu.classes.GetBaseClass(classID)
if baseClass != classID && baseClass != ClassCommoner {
progression = append(progression, baseClass)
}
// Add secondary base class if different
secondaryBase := cu.classes.GetSecondaryBaseClass(classID)
if secondaryBase != classID && secondaryBase != baseClass && secondaryBase != ClassCommoner {
progression = append(progression, secondaryBase)
}
// Add the final class
progression = append(progression, classID)
return progression
}
// GetClassesForBaseClass returns all classes that belong to a base class
func (cu *ClassUtils) GetClassesForBaseClass(baseClassID int8) []int8 {
result := make([]int8, 0)
allClasses := cu.classes.GetAllClasses()
for classID := range allClasses {
if cu.classes.GetBaseClass(classID) == baseClassID {
result = append(result, classID)
}
}
return result
}
// GetClassesBySecondaryBase returns all classes that belong to a secondary base class
func (cu *ClassUtils) GetClassesBySecondaryBase(secondaryBaseID int8) []int8 {
result := make([]int8, 0)
allClasses := cu.classes.GetAllClasses()
for classID := range allClasses {
if cu.classes.GetSecondaryBaseClass(classID) == secondaryBaseID {
result = append(result, classID)
}
}
return result
}
// GetClassesByPattern returns classes matching a name pattern
func (cu *ClassUtils) GetClassesByPattern(pattern string) []int8 {
pattern = strings.ToLower(pattern)
result := make([]int8, 0)
allClasses := cu.classes.GetAllClasses()
for classID, displayName := range allClasses {
if strings.Contains(strings.ToLower(displayName), pattern) {
result = append(result, classID)
}
}
return result
}
// ValidateClassTransition checks if a class change is allowed
func (cu *ClassUtils) ValidateClassTransition(fromClassID, toClassID int8) (bool, string) {
if !cu.classes.IsValidClassID(fromClassID) {
return false, "Invalid source class"
}
if !cu.classes.IsValidClassID(toClassID) {
return false, "Invalid target class"
}
if fromClassID == toClassID {
return false, "Cannot change to the same class"
}
// Basic progression validation - can only advance, not go backward
fromProgression := cu.GetClassProgression(fromClassID)
toProgression := cu.GetClassProgression(toClassID)
// Check if the target class is a valid advancement
if len(toProgression) <= len(fromProgression) {
return false, "Cannot regress to a lower tier class"
}
// Check if the progressions are compatible (share the same base path)
for i := 0; i < len(fromProgression); i++ {
if i >= len(toProgression) || fromProgression[i] != toProgression[i] {
return false, "Incompatible class progression paths"
}
}
return true, ""
}
// GetClassAliases returns common aliases for a class
func (cu *ClassUtils) GetClassAliases(classID int8) []string {
aliases := make([]string, 0)
switch classID {
case ClassShadowknight:
aliases = append(aliases, "SK", "Shadow Knight", "Dark Knight")
case ClassSwashbuckler:
aliases = append(aliases, "Swash", "Swashy")
case ClassTroubador:
aliases = append(aliases, "Troub", "Troubadour")
case ClassIllusionist:
aliases = append(aliases, "Illy", "Illusion")
case ClassConjuror:
aliases = append(aliases, "Conj", "Conjurer")
case ClassNecromancer:
aliases = append(aliases, "Necro", "Nec")
case ClassBeastlord:
aliases = append(aliases, "BL", "Beast Lord")
case ClassWeaponsmith:
aliases = append(aliases, "WS", "Weapon Smith")
}
// Always include the official names
aliases = append(aliases, cu.classes.GetClassName(classID))
aliases = append(aliases, cu.classes.GetClassNameCase(classID))
return aliases
}
// GetClassStatistics returns statistics about the class system
func (cu *ClassUtils) GetClassStatistics() map[string]any {
stats := make(map[string]any)
allClasses := cu.classes.GetAllClasses()
stats["total_classes"] = len(allClasses)
adventureCount := 0
tradeskillCount := 0
specialCount := 0
for classID := range allClasses {
switch cu.classes.GetClassType(classID) {
case ClassTypeAdventure:
adventureCount++
case ClassTypeTradeskill:
tradeskillCount++
default:
specialCount++
}
}
stats["adventure_classes"] = adventureCount
stats["tradeskill_classes"] = tradeskillCount
stats["special_classes"] = specialCount
// Base class distribution
baseClassDistribution := make(map[string][]string)
for classID, displayName := range allClasses {
if cu.classes.IsAdventureClass(classID) {
baseClassID := cu.classes.GetBaseClass(classID)
baseClassName := cu.classes.GetClassNameCase(baseClassID)
baseClassDistribution[baseClassName] = append(baseClassDistribution[baseClassName], displayName)
}
}
stats["base_class_distribution"] = baseClassDistribution
return stats
}
// FormatClassList returns a formatted string of class names
func (cu *ClassUtils) FormatClassList(classIDs []int8, separator string) string {
if len(classIDs) == 0 {
return ""
}
names := make([]string, len(classIDs))
for i, classID := range classIDs {
names[i] = cu.classes.GetClassNameCase(classID)
}
return strings.Join(names, separator)
}
// GetEQClassName returns the EQ-style class name for a given class and level
// This is a placeholder for the original C++ GetEQClassName functionality
func (cu *ClassUtils) GetEQClassName(classID int8, level int8) string {
// TODO: Implement level-based class names when level system is available
// For now, just return the display name
return cu.classes.GetClassNameCase(classID)
}
// GetStartingClass returns the appropriate starting class for character creation
func (cu *ClassUtils) GetStartingClass() int8 {
return ClassCommoner
}
// IsBaseClass checks if a class is a base class (Fighter, Priest, Mage, Scout)
func (cu *ClassUtils) IsBaseClass(classID int8) bool {
return classID == ClassFighter || classID == ClassPriest || classID == ClassMage || classID == ClassScout
}
// IsSecondaryBaseClass checks if a class is a secondary base class
func (cu *ClassUtils) IsSecondaryBaseClass(classID int8) bool {
// Check if any class has this as their secondary base
allClasses := cu.classes.GetAllClasses()
for checkClassID := range allClasses {
if cu.classes.GetSecondaryBaseClass(checkClassID) == classID && checkClassID != classID {
return true
}
}
return false
}