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"
|
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