modernize class package
This commit is contained in:
parent
79ee999150
commit
a47ad4f737
359
internal/classes/class.go
Normal file
359
internal/classes/class.go
Normal 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
|
||||
}
|
372
internal/classes/class_test.go
Normal file
372
internal/classes/class_test.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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
@ -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
47
internal/classes/doc.go
Normal 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
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user