simplify/ehance classes
This commit is contained in:
parent
32143aab1a
commit
f99343f490
@ -1,359 +0,0 @@
|
||||
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
|
||||
}
|
540
internal/classes/classes.go
Normal file
540
internal/classes/classes.go
Normal file
@ -0,0 +1,540 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Class represents a character class with all its properties
|
||||
type Class struct {
|
||||
ID int8 `json:"id"`
|
||||
Name string `json:"name"` // Uppercase name (WARRIOR)
|
||||
DisplayName string `json:"display_name"` // Friendly name (Warrior)
|
||||
BaseClass int8 `json:"base_class"` // Primary archetype
|
||||
SecondaryBaseClass int8 `json:"secondary_base"` // Secondary archetype
|
||||
ClassType string `json:"type"` // adventure/tradeskill/special
|
||||
IsAdventure bool `json:"is_adventure"`
|
||||
IsTradeskill bool `json:"is_tradeskill"`
|
||||
}
|
||||
|
||||
// GetID returns the class ID
|
||||
func (c *Class) GetID() int8 {
|
||||
return c.ID
|
||||
}
|
||||
|
||||
// GetName returns the uppercase class name (C++ API compatibility)
|
||||
func (c *Class) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// GetDisplayName returns the friendly display name
|
||||
func (c *Class) GetDisplayName() string {
|
||||
return c.DisplayName
|
||||
}
|
||||
|
||||
// GetBaseClass returns the base archetype (C++ API compatibility)
|
||||
func (c *Class) GetBaseClass() int8 {
|
||||
return c.BaseClass
|
||||
}
|
||||
|
||||
// GetSecondaryBaseClass returns the secondary archetype (C++ API compatibility)
|
||||
func (c *Class) GetSecondaryBaseClass() int8 {
|
||||
return c.SecondaryBaseClass
|
||||
}
|
||||
|
||||
// IsValid returns true if the class ID is valid
|
||||
func (c *Class) IsValid() bool {
|
||||
return c.ID >= MinClassID && c.ID <= MaxClassID
|
||||
}
|
||||
|
||||
// Manager provides centralized class management with caching and lookup optimization
|
||||
type Manager struct {
|
||||
classes map[int8]*Class // ID -> Class
|
||||
nameToID map[string]int8 // Uppercase name -> ID
|
||||
displayNameMap map[int8]string // ID -> Display name
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Statistics
|
||||
stats struct {
|
||||
ClassesLoaded int32
|
||||
LookupsPerformed int32
|
||||
CacheHits int32
|
||||
CacheMisses int32
|
||||
}
|
||||
}
|
||||
|
||||
// NewManager creates a new class manager
|
||||
func NewManager() *Manager {
|
||||
manager := &Manager{
|
||||
classes: make(map[int8]*Class),
|
||||
nameToID: make(map[string]int8),
|
||||
displayNameMap: make(map[int8]string),
|
||||
}
|
||||
|
||||
// Initialize with all classes
|
||||
manager.initializeClasses()
|
||||
return manager
|
||||
}
|
||||
|
||||
// initializeClasses populates the manager with all class data
|
||||
func (m *Manager) initializeClasses() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// Define all classes with their properties
|
||||
classDefinitions := []struct {
|
||||
id int8
|
||||
name string
|
||||
displayName string
|
||||
baseClass int8
|
||||
secondaryBase int8
|
||||
classType string
|
||||
isAdventure bool
|
||||
isTradeskill bool
|
||||
}{
|
||||
// Base classes
|
||||
{ClassCommoner, "COMMONER", "Commoner", ClassCommoner, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassFighter, "FIGHTER", "Fighter", ClassFighter, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassPriest, "PRIEST", "Priest", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassMage, "MAGE", "Mage", ClassMage, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassScout, "SCOUT", "Scout", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
|
||||
// Fighter subclasses
|
||||
{ClassWarrior, "WARRIOR", "Warrior", ClassFighter, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassGuardian, "GUARDIAN", "Guardian", ClassFighter, ClassWarrior, ClassTypeAdventure, true, false},
|
||||
{ClassBerserker, "BERSERKER", "Berserker", ClassFighter, ClassWarrior, ClassTypeAdventure, true, false},
|
||||
{ClassBrawler, "BRAWLER", "Brawler", ClassFighter, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassMonk, "MONK", "Monk", ClassFighter, ClassBrawler, ClassTypeAdventure, true, false},
|
||||
{ClassBruiser, "BRUISER", "Bruiser", ClassFighter, ClassBrawler, ClassTypeAdventure, true, false},
|
||||
{ClassCrusader, "CRUSADER", "Crusader", ClassFighter, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassShadowknight, "SHADOWKNIGHT", "Shadowknight", ClassFighter, ClassCrusader, ClassTypeAdventure, true, false},
|
||||
{ClassPaladin, "PALADIN", "Paladin", ClassFighter, ClassCrusader, ClassTypeAdventure, true, false},
|
||||
|
||||
// Priest subclasses
|
||||
{ClassCleric, "CLERIC", "Cleric", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassTemplar, "TEMPLAR", "Templar", ClassPriest, ClassCleric, ClassTypeAdventure, true, false},
|
||||
{ClassInquisitor, "INQUISITOR", "Inquisitor", ClassPriest, ClassCleric, ClassTypeAdventure, true, false},
|
||||
{ClassDruid, "DRUID", "Druid", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassWarden, "WARDEN", "Warden", ClassPriest, ClassDruid, ClassTypeAdventure, true, false},
|
||||
{ClassFury, "FURY", "Fury", ClassPriest, ClassDruid, ClassTypeAdventure, true, false},
|
||||
{ClassShaman, "SHAMAN", "Shaman", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassMystic, "MYSTIC", "Mystic", ClassPriest, ClassShaman, ClassTypeAdventure, true, false},
|
||||
{ClassDefiler, "DEFILER", "Defiler", ClassPriest, ClassShaman, ClassTypeAdventure, true, false},
|
||||
|
||||
// Mage subclasses
|
||||
{ClassSorcerer, "SORCERER", "Sorcerer", ClassMage, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassWizard, "WIZARD", "Wizard", ClassMage, ClassSorcerer, ClassTypeAdventure, true, false},
|
||||
{ClassWarlock, "WARLOCK", "Warlock", ClassMage, ClassSorcerer, ClassTypeAdventure, true, false},
|
||||
{ClassEnchanter, "ENCHANTER", "Enchanter", ClassMage, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassIllusionist, "ILLUSIONIST", "Illusionist", ClassMage, ClassEnchanter, ClassTypeAdventure, true, false},
|
||||
{ClassCoercer, "COERCER", "Coercer", ClassMage, ClassEnchanter, ClassTypeAdventure, true, false},
|
||||
{ClassSummoner, "SUMMONER", "Summoner", ClassMage, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassConjuror, "CONJUROR", "Conjuror", ClassMage, ClassSummoner, ClassTypeAdventure, true, false},
|
||||
{ClassNecromancer, "NECROMANCER", "Necromancer", ClassMage, ClassSummoner, ClassTypeAdventure, true, false},
|
||||
|
||||
// Scout subclasses
|
||||
{ClassRogue, "ROGUE", "Rogue", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassSwashbuckler, "SWASHBUCKLER", "Swashbuckler", ClassScout, ClassRogue, ClassTypeAdventure, true, false},
|
||||
{ClassBrigand, "BRIGAND", "Brigand", ClassScout, ClassRogue, ClassTypeAdventure, true, false},
|
||||
{ClassBard, "BARD", "Bard", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassTroubador, "TROUBADOR", "Troubador", ClassScout, ClassBard, ClassTypeAdventure, true, false},
|
||||
{ClassDirge, "DIRGE", "Dirge", ClassScout, ClassBard, ClassTypeAdventure, true, false},
|
||||
{ClassPredator, "PREDATOR", "Predator", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassRanger, "RANGER", "Ranger", ClassScout, ClassPredator, ClassTypeAdventure, true, false},
|
||||
{ClassAssassin, "ASSASSIN", "Assassin", ClassScout, ClassPredator, ClassTypeAdventure, true, false},
|
||||
{ClassAnimalist, "ANIMALIST", "Animalist", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassBeastlord, "BEASTLORD", "Beastlord", ClassScout, ClassAnimalist, ClassTypeAdventure, true, false},
|
||||
|
||||
// Special classes
|
||||
{ClassShaper, "SHAPER", "Shaper", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
|
||||
{ClassChanneler, "CHANNELER", "Channeler", ClassPriest, ClassShaper, ClassTypeAdventure, true, false},
|
||||
|
||||
// Tradeskill classes
|
||||
{ClassArtisan, "ARTISAN", "Artisan", ClassArtisan, ClassCommoner, ClassTypeTradeskill, false, true},
|
||||
{ClassCraftsman, "CRAFTSMAN", "Craftsman", ClassArtisan, ClassCommoner, ClassTypeTradeskill, false, true},
|
||||
{ClassProvisioner, "PROVISIONER", "Provisioner", ClassArtisan, ClassCraftsman, ClassTypeTradeskill, false, true},
|
||||
{ClassWoodworker, "WOODWORKER", "Woodworker", ClassArtisan, ClassCraftsman, ClassTypeTradeskill, false, true},
|
||||
{ClassCarpenter, "CARPENTER", "Carpenter", ClassArtisan, ClassCraftsman, ClassTypeTradeskill, false, true},
|
||||
{ClassOutfitter, "OUTFITTER", "Outfitter", ClassArtisan, ClassCommoner, ClassTypeTradeskill, false, true},
|
||||
{ClassArmorer, "ARMORER", "Armorer", ClassArtisan, ClassOutfitter, ClassTypeTradeskill, false, true},
|
||||
{ClassWeaponsmith, "WEAPONSMITH", "Weaponsmith", ClassArtisan, ClassOutfitter, ClassTypeTradeskill, false, true},
|
||||
{ClassTailor, "TAILOR", "Tailor", ClassArtisan, ClassOutfitter, ClassTypeTradeskill, false, true},
|
||||
{ClassScholar, "SCHOLAR", "Scholar", ClassArtisan, ClassCommoner, ClassTypeTradeskill, false, true},
|
||||
{ClassJeweler, "JEWELER", "Jeweler", ClassArtisan, ClassScholar, ClassTypeTradeskill, false, true},
|
||||
{ClassSage, "SAGE", "Sage", ClassArtisan, ClassScholar, ClassTypeTradeskill, false, true},
|
||||
{ClassAlchemist, "ALCHEMIST", "Alchemist", ClassArtisan, ClassScholar, ClassTypeTradeskill, false, true},
|
||||
}
|
||||
|
||||
// Create and store class objects
|
||||
for _, def := range classDefinitions {
|
||||
class := &Class{
|
||||
ID: def.id,
|
||||
Name: def.name,
|
||||
DisplayName: def.displayName,
|
||||
BaseClass: def.baseClass,
|
||||
SecondaryBaseClass: def.secondaryBase,
|
||||
ClassType: def.classType,
|
||||
IsAdventure: def.isAdventure,
|
||||
IsTradeskill: def.isTradeskill,
|
||||
}
|
||||
|
||||
m.classes[def.id] = class
|
||||
m.nameToID[def.name] = def.id
|
||||
m.displayNameMap[def.id] = def.displayName
|
||||
}
|
||||
|
||||
m.stats.ClassesLoaded = int32(len(classDefinitions))
|
||||
}
|
||||
|
||||
// GetClass gets class by ID
|
||||
func (m *Manager) GetClass(classID int8) *Class {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
m.stats.LookupsPerformed++
|
||||
|
||||
if class, exists := m.classes[classID]; exists {
|
||||
m.stats.CacheHits++
|
||||
return class
|
||||
}
|
||||
|
||||
m.stats.CacheMisses++
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetClassID returns the class ID for a given class name (C++ API compatibility)
|
||||
func (m *Manager) GetClassID(name string) int8 {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
m.stats.LookupsPerformed++
|
||||
|
||||
className := strings.ToUpper(strings.TrimSpace(name))
|
||||
if classID, exists := m.nameToID[className]; exists {
|
||||
m.stats.CacheHits++
|
||||
return classID
|
||||
}
|
||||
|
||||
m.stats.CacheMisses++
|
||||
return -1 // Invalid class
|
||||
}
|
||||
|
||||
// GetClassName returns the uppercase class name for a given ID (C++ API compatibility)
|
||||
func (m *Manager) GetClassName(classID int8) string {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
m.stats.LookupsPerformed++
|
||||
|
||||
if class := m.classes[classID]; class != nil {
|
||||
m.stats.CacheHits++
|
||||
return class.Name
|
||||
}
|
||||
|
||||
m.stats.CacheMisses++
|
||||
return "" // Invalid class ID
|
||||
}
|
||||
|
||||
// GetClassNameCase returns the friendly display name for a given class ID (C++ API compatibility)
|
||||
func (m *Manager) GetClassNameCase(classID int8) string {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
m.stats.LookupsPerformed++
|
||||
|
||||
if displayName, exists := m.displayNameMap[classID]; exists {
|
||||
m.stats.CacheHits++
|
||||
return displayName
|
||||
}
|
||||
|
||||
m.stats.CacheMisses++
|
||||
return "" // Invalid class ID
|
||||
}
|
||||
|
||||
// GetBaseClass returns the base class ID for a given class (C++ API compatibility)
|
||||
func (m *Manager) GetBaseClass(classID int8) int8 {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
m.stats.LookupsPerformed++
|
||||
|
||||
if class := m.classes[classID]; class != nil {
|
||||
m.stats.CacheHits++
|
||||
return class.BaseClass
|
||||
}
|
||||
|
||||
m.stats.CacheMisses++
|
||||
return ClassCommoner // Default for unknown classes
|
||||
}
|
||||
|
||||
// GetSecondaryBaseClass returns the secondary base class ID (C++ API compatibility)
|
||||
func (m *Manager) GetSecondaryBaseClass(classID int8) int8 {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
m.stats.LookupsPerformed++
|
||||
|
||||
if class := m.classes[classID]; class != nil {
|
||||
m.stats.CacheHits++
|
||||
return class.SecondaryBaseClass
|
||||
}
|
||||
|
||||
m.stats.CacheMisses++
|
||||
return ClassCommoner // Default for unknown classes
|
||||
}
|
||||
|
||||
// GetTSBaseClass returns the tradeskill base class ID (C++ API compatibility)
|
||||
func (m *Manager) 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 (C++ API compatibility)
|
||||
func (m *Manager) 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 (m *Manager) IsValidClassID(classID int8) bool {
|
||||
return classID >= MinClassID && classID <= MaxClassID
|
||||
}
|
||||
|
||||
// IsAdventureClass checks if a class is an adventure class
|
||||
func (m *Manager) IsAdventureClass(classID int8) bool {
|
||||
if class := m.GetClass(classID); class != nil {
|
||||
return class.IsAdventure
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsTradeskillClass checks if a class is a tradeskill class
|
||||
func (m *Manager) IsTradeskillClass(classID int8) bool {
|
||||
if class := m.GetClass(classID); class != nil {
|
||||
return class.IsTradeskill
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetClassType returns the type of class (adventure, tradeskill, etc.)
|
||||
func (m *Manager) GetClassType(classID int8) string {
|
||||
if class := m.GetClass(classID); class != nil {
|
||||
return class.ClassType
|
||||
}
|
||||
return ClassTypeSpecial
|
||||
}
|
||||
|
||||
// GetAllClasses returns all class IDs and their display names
|
||||
func (m *Manager) GetAllClasses() map[int8]string {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
result := make(map[int8]string)
|
||||
for classID, displayName := range m.displayNameMap {
|
||||
result[classID] = displayName
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetClassInfo returns comprehensive information about a class
|
||||
func (m *Manager) GetClassInfo(classID int8) map[string]any {
|
||||
class := m.GetClass(classID)
|
||||
info := make(map[string]any)
|
||||
|
||||
if class == nil {
|
||||
info["valid"] = false
|
||||
return info
|
||||
}
|
||||
|
||||
info["valid"] = true
|
||||
info["class_id"] = class.ID
|
||||
info["name"] = class.Name
|
||||
info["display_name"] = class.DisplayName
|
||||
info["base_class"] = class.BaseClass
|
||||
info["secondary_base_class"] = class.SecondaryBaseClass
|
||||
info["type"] = class.ClassType
|
||||
info["is_adventure"] = class.IsAdventure
|
||||
info["is_tradeskill"] = class.IsTradeskill
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// GetClassHierarchy returns the full class hierarchy for a given class
|
||||
func (m *Manager) GetClassHierarchy(classID int8) []int8 {
|
||||
class := m.GetClass(classID)
|
||||
if class == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
hierarchy := []int8{classID}
|
||||
|
||||
// Add secondary base class if it exists
|
||||
if class.SecondaryBaseClass != ClassCommoner && class.SecondaryBaseClass != classID {
|
||||
hierarchy = append(hierarchy, class.SecondaryBaseClass)
|
||||
}
|
||||
|
||||
// Add base class
|
||||
if class.BaseClass != ClassCommoner && class.BaseClass != classID {
|
||||
// Check if base is already in hierarchy (from secondary)
|
||||
found := false
|
||||
for _, id := range hierarchy {
|
||||
if id == class.BaseClass {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
hierarchy = append(hierarchy, class.BaseClass)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (m *Manager) IsSameArchetype(classID1, classID2 int8) bool {
|
||||
class1 := m.GetClass(classID1)
|
||||
class2 := m.GetClass(classID2)
|
||||
|
||||
if class1 == nil || class2 == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return class1.BaseClass == class2.BaseClass
|
||||
}
|
||||
|
||||
// GetArchetypeClasses returns all classes of a given archetype
|
||||
func (m *Manager) GetArchetypeClasses(archetypeID int8) []int8 {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
var classes []int8
|
||||
for classID, class := range m.classes {
|
||||
if class.BaseClass == archetypeID {
|
||||
classes = append(classes, classID)
|
||||
}
|
||||
}
|
||||
return classes
|
||||
}
|
||||
|
||||
// GetStatistics returns current manager statistics
|
||||
func (m *Manager) GetStatistics() map[string]interface{} {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
return map[string]interface{}{
|
||||
"classes_loaded": m.stats.ClassesLoaded,
|
||||
"lookups_performed": m.stats.LookupsPerformed,
|
||||
"cache_hits": m.stats.CacheHits,
|
||||
"cache_misses": m.stats.CacheMisses,
|
||||
"total_classes": int32(len(m.classes)),
|
||||
"adventure_classes": m.countClassesByType(ClassTypeAdventure),
|
||||
"tradeskill_classes": m.countClassesByType(ClassTypeTradeskill),
|
||||
}
|
||||
}
|
||||
|
||||
// countClassesByType counts classes of a specific type
|
||||
func (m *Manager) countClassesByType(classType string) int32 {
|
||||
count := int32(0)
|
||||
for _, class := range m.classes {
|
||||
if class.ClassType == classType {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Global manager instance
|
||||
var globalManager *Manager
|
||||
|
||||
// InitializeManager initializes the global class manager
|
||||
func InitializeManager() {
|
||||
globalManager = NewManager()
|
||||
}
|
||||
|
||||
// GetManager returns the global class manager
|
||||
func GetManager() *Manager {
|
||||
if globalManager == nil {
|
||||
InitializeManager()
|
||||
}
|
||||
return globalManager
|
||||
}
|
||||
|
||||
// Global functions for C++ API compatibility
|
||||
func GetClassID(name string) int8 {
|
||||
return GetManager().GetClassID(name)
|
||||
}
|
||||
|
||||
func GetClassName(classID int8) string {
|
||||
return GetManager().GetClassName(classID)
|
||||
}
|
||||
|
||||
func GetClassNameCase(classID int8) string {
|
||||
return GetManager().GetClassNameCase(classID)
|
||||
}
|
||||
|
||||
func GetBaseClass(classID int8) int8 {
|
||||
return GetManager().GetBaseClass(classID)
|
||||
}
|
||||
|
||||
func GetSecondaryBaseClass(classID int8) int8 {
|
||||
return GetManager().GetSecondaryBaseClass(classID)
|
||||
}
|
||||
|
||||
func GetTSBaseClass(classID int8) int8 {
|
||||
return GetManager().GetTSBaseClass(classID)
|
||||
}
|
||||
|
||||
func GetSecondaryTSBaseClass(classID int8) int8 {
|
||||
return GetManager().GetSecondaryTSBaseClass(classID)
|
||||
}
|
||||
|
||||
func IsValidClassID(classID int8) bool {
|
||||
return GetManager().IsValidClassID(classID)
|
||||
}
|
||||
|
||||
func IsAdventureClass(classID int8) bool {
|
||||
return GetManager().IsAdventureClass(classID)
|
||||
}
|
||||
|
||||
func IsTradeskillClass(classID int8) bool {
|
||||
return GetManager().IsTradeskillClass(classID)
|
||||
}
|
||||
|
||||
func GetClassType(classID int8) string {
|
||||
return GetManager().GetClassType(classID)
|
||||
}
|
||||
|
||||
func GetAllClasses() map[int8]string {
|
||||
return GetManager().GetAllClasses()
|
||||
}
|
||||
|
||||
func GetClassInfo(classID int8) map[string]any {
|
||||
return GetManager().GetClassInfo(classID)
|
||||
}
|
||||
|
||||
func GetClassHierarchy(classID int8) []int8 {
|
||||
return GetManager().GetClassHierarchy(classID)
|
||||
}
|
||||
|
||||
func IsSameArchetype(classID1, classID2 int8) bool {
|
||||
return GetManager().IsSameArchetype(classID1, classID2)
|
||||
}
|
||||
|
||||
func GetArchetypeClasses(archetypeID int8) []int8 {
|
||||
return GetManager().GetArchetypeClasses(archetypeID)
|
||||
}
|
578
internal/classes/classes_test.go
Normal file
578
internal/classes/classes_test.go
Normal file
@ -0,0 +1,578 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClassBasics(t *testing.T) {
|
||||
class := &Class{
|
||||
ID: ClassWarrior,
|
||||
Name: "WARRIOR",
|
||||
DisplayName: "Warrior",
|
||||
BaseClass: ClassFighter,
|
||||
SecondaryBaseClass: ClassCommoner,
|
||||
ClassType: ClassTypeAdventure,
|
||||
IsAdventure: true,
|
||||
IsTradeskill: false,
|
||||
}
|
||||
|
||||
if class.GetID() != ClassWarrior {
|
||||
t.Errorf("Expected ID %d, got %d", ClassWarrior, class.GetID())
|
||||
}
|
||||
|
||||
if class.GetName() != "WARRIOR" {
|
||||
t.Errorf("Expected name 'WARRIOR', got %s", class.GetName())
|
||||
}
|
||||
|
||||
if class.GetDisplayName() != "Warrior" {
|
||||
t.Errorf("Expected display name 'Warrior', got %s", class.GetDisplayName())
|
||||
}
|
||||
|
||||
if class.GetBaseClass() != ClassFighter {
|
||||
t.Errorf("Expected base class %d, got %d", ClassFighter, class.GetBaseClass())
|
||||
}
|
||||
|
||||
if class.GetSecondaryBaseClass() != ClassCommoner {
|
||||
t.Errorf("Expected secondary base class %d, got %d", ClassCommoner, class.GetSecondaryBaseClass())
|
||||
}
|
||||
|
||||
if !class.IsValid() {
|
||||
t.Error("Expected class to be valid")
|
||||
}
|
||||
|
||||
// Test invalid class
|
||||
invalidClass := &Class{ID: int8(99)}
|
||||
if invalidClass.IsValid() {
|
||||
t.Error("Expected invalid class to be invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerCreation(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
if manager == nil {
|
||||
t.Fatal("Manager creation failed")
|
||||
}
|
||||
|
||||
stats := manager.GetStatistics()
|
||||
if stats["classes_loaded"].(int32) == 0 {
|
||||
t.Error("Expected classes to be loaded during initialization")
|
||||
}
|
||||
|
||||
if stats["total_classes"].(int32) != MaxClasses {
|
||||
t.Errorf("Expected %d total classes, got %d", MaxClasses, stats["total_classes"])
|
||||
}
|
||||
|
||||
t.Logf("Manager initialized with %d classes", stats["total_classes"])
|
||||
}
|
||||
|
||||
func TestGetClassID(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test valid class names (C++ API compatibility)
|
||||
testCases := map[string]int8{
|
||||
"WARRIOR": ClassWarrior,
|
||||
"GUARDIAN": ClassGuardian,
|
||||
"TEMPLAR": ClassTemplar,
|
||||
"WIZARD": ClassWizard,
|
||||
"RANGER": ClassRanger,
|
||||
"BEASTLORD": ClassBeastlord,
|
||||
"CHANNELER": ClassChanneler,
|
||||
"ARTISAN": ClassArtisan,
|
||||
"PROVISIONER": ClassProvisioner,
|
||||
"ALCHEMIST": ClassAlchemist,
|
||||
"COMMONER": ClassCommoner,
|
||||
}
|
||||
|
||||
for name, expectedID := range testCases {
|
||||
id := manager.GetClassID(name)
|
||||
if id != expectedID {
|
||||
t.Errorf("GetClassID('%s'): expected %d, got %d", name, expectedID, id)
|
||||
}
|
||||
|
||||
// Test case insensitivity and whitespace handling
|
||||
id = manager.GetClassID(" " + strings.ToLower(name) + " ")
|
||||
if id != expectedID {
|
||||
t.Errorf("GetClassID('%s') with whitespace and lowercase: expected %d, got %d", name, expectedID, id)
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid class name
|
||||
invalidID := manager.GetClassID("INVALID_CLASS")
|
||||
if invalidID != -1 {
|
||||
t.Errorf("Expected -1 for invalid class name, got %d", invalidID)
|
||||
}
|
||||
|
||||
// Test empty string
|
||||
emptyID := manager.GetClassID("")
|
||||
if emptyID != -1 {
|
||||
t.Errorf("Expected -1 for empty class name, got %d", emptyID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClassName(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test valid class IDs (C++ API compatibility)
|
||||
testCases := map[int8]string{
|
||||
ClassWarrior: "WARRIOR",
|
||||
ClassGuardian: "GUARDIAN",
|
||||
ClassTemplar: "TEMPLAR",
|
||||
ClassWizard: "WIZARD",
|
||||
ClassRanger: "RANGER",
|
||||
ClassBeastlord: "BEASTLORD",
|
||||
ClassChanneler: "CHANNELER",
|
||||
ClassArtisan: "ARTISAN",
|
||||
ClassProvisioner: "PROVISIONER",
|
||||
ClassAlchemist: "ALCHEMIST",
|
||||
ClassCommoner: "COMMONER",
|
||||
}
|
||||
|
||||
for classID, expectedName := range testCases {
|
||||
name := manager.GetClassName(classID)
|
||||
if name != expectedName {
|
||||
t.Errorf("GetClassName(%d): expected '%s', got '%s'", classID, expectedName, name)
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid class ID
|
||||
invalidName := manager.GetClassName(int8(99))
|
||||
if invalidName != "" {
|
||||
t.Errorf("Expected empty string for invalid class ID, got '%s'", invalidName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClassNameCase(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test display names (C++ API compatibility)
|
||||
testCases := map[int8]string{
|
||||
ClassWarrior: "Warrior",
|
||||
ClassGuardian: "Guardian",
|
||||
ClassTemplar: "Templar",
|
||||
ClassWizard: "Wizard",
|
||||
ClassRanger: "Ranger",
|
||||
ClassBeastlord: "Beastlord",
|
||||
ClassChanneler: "Channeler",
|
||||
ClassArtisan: "Artisan",
|
||||
ClassProvisioner: "Provisioner",
|
||||
ClassAlchemist: "Alchemist",
|
||||
ClassCommoner: "Commoner",
|
||||
}
|
||||
|
||||
for classID, expectedDisplayName := range testCases {
|
||||
displayName := manager.GetClassNameCase(classID)
|
||||
if displayName != expectedDisplayName {
|
||||
t.Errorf("GetClassNameCase(%d): expected '%s', got '%s'", classID, expectedDisplayName, displayName)
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid class ID
|
||||
invalidDisplayName := manager.GetClassNameCase(int8(99))
|
||||
if invalidDisplayName != "" {
|
||||
t.Errorf("Expected empty string for invalid class ID, got '%s'", invalidDisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBaseClass(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test base class mappings (C++ API compatibility)
|
||||
testCases := map[int8]int8{
|
||||
// Fighter archetype
|
||||
ClassWarrior: ClassFighter,
|
||||
ClassGuardian: ClassFighter,
|
||||
ClassBerserker: ClassFighter,
|
||||
ClassMonk: ClassFighter,
|
||||
ClassShadowknight: ClassFighter,
|
||||
ClassPaladin: ClassFighter,
|
||||
|
||||
// Priest archetype
|
||||
ClassCleric: ClassPriest,
|
||||
ClassTemplar: ClassPriest,
|
||||
ClassInquisitor: ClassPriest,
|
||||
ClassWarden: ClassPriest,
|
||||
ClassFury: ClassPriest,
|
||||
ClassMystic: ClassPriest,
|
||||
ClassDefiler: ClassPriest,
|
||||
ClassChanneler: ClassPriest,
|
||||
|
||||
// Mage archetype
|
||||
ClassSorcerer: ClassMage,
|
||||
ClassWizard: ClassMage,
|
||||
ClassWarlock: ClassMage,
|
||||
ClassIllusionist: ClassMage,
|
||||
ClassCoercer: ClassMage,
|
||||
ClassConjuror: ClassMage,
|
||||
ClassNecromancer: ClassMage,
|
||||
|
||||
// Scout archetype
|
||||
ClassRogue: ClassScout,
|
||||
ClassSwashbuckler: ClassScout,
|
||||
ClassBrigand: ClassScout,
|
||||
ClassTroubador: ClassScout,
|
||||
ClassDirge: ClassScout,
|
||||
ClassRanger: ClassScout,
|
||||
ClassAssassin: ClassScout,
|
||||
ClassBeastlord: ClassScout,
|
||||
|
||||
// Base classes should return themselves
|
||||
ClassFighter: ClassFighter,
|
||||
ClassPriest: ClassPriest,
|
||||
ClassMage: ClassMage,
|
||||
ClassScout: ClassScout,
|
||||
ClassCommoner: ClassCommoner,
|
||||
}
|
||||
|
||||
for classID, expectedBase := range testCases {
|
||||
base := manager.GetBaseClass(classID)
|
||||
if base != expectedBase {
|
||||
t.Errorf("GetBaseClass(%d): expected %d, got %d", classID, expectedBase, base)
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid class ID
|
||||
invalidBase := manager.GetBaseClass(int8(99))
|
||||
if invalidBase != ClassCommoner {
|
||||
t.Errorf("Expected %d (Commoner) for invalid class ID, got %d", ClassCommoner, invalidBase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSecondaryBaseClass(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test secondary base class mappings (C++ API compatibility)
|
||||
testCases := map[int8]int8{
|
||||
ClassGuardian: ClassWarrior,
|
||||
ClassBerserker: ClassWarrior,
|
||||
ClassMonk: ClassBrawler,
|
||||
ClassBruiser: ClassBrawler,
|
||||
ClassShadowknight: ClassCrusader,
|
||||
ClassPaladin: ClassCrusader,
|
||||
ClassTemplar: ClassCleric,
|
||||
ClassInquisitor: ClassCleric,
|
||||
ClassWarden: ClassDruid,
|
||||
ClassFury: ClassDruid,
|
||||
ClassMystic: ClassShaman,
|
||||
ClassDefiler: ClassShaman,
|
||||
ClassWizard: ClassSorcerer,
|
||||
ClassWarlock: ClassSorcerer,
|
||||
ClassIllusionist: ClassEnchanter,
|
||||
ClassCoercer: ClassEnchanter,
|
||||
ClassConjuror: ClassSummoner,
|
||||
ClassNecromancer: ClassSummoner,
|
||||
ClassSwashbuckler: ClassRogue,
|
||||
ClassBrigand: ClassRogue,
|
||||
ClassTroubador: ClassBard,
|
||||
ClassDirge: ClassBard,
|
||||
ClassRanger: ClassPredator,
|
||||
ClassAssassin: ClassPredator,
|
||||
ClassBeastlord: ClassAnimalist,
|
||||
ClassChanneler: ClassShaper,
|
||||
}
|
||||
|
||||
for classID, expectedSecondary := range testCases {
|
||||
secondary := manager.GetSecondaryBaseClass(classID)
|
||||
if secondary != expectedSecondary {
|
||||
t.Errorf("GetSecondaryBaseClass(%d): expected %d, got %d", classID, expectedSecondary, secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Test base classes (should return Commoner)
|
||||
baseClasses := []int8{ClassCommoner, ClassFighter, ClassPriest, ClassMage, ClassScout}
|
||||
for _, classID := range baseClasses {
|
||||
secondary := manager.GetSecondaryBaseClass(classID)
|
||||
if secondary != ClassCommoner {
|
||||
t.Errorf("GetSecondaryBaseClass(%d): expected %d (Commoner), got %d", classID, ClassCommoner, secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassValidation(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test valid class IDs
|
||||
validClasses := []int8{
|
||||
ClassCommoner, ClassFighter, ClassWarrior, ClassGuardian,
|
||||
ClassTemplar, ClassWizard, ClassRanger, ClassBeastlord,
|
||||
ClassChanneler, ClassArtisan, ClassAlchemist,
|
||||
}
|
||||
|
||||
for _, classID := range validClasses {
|
||||
if !manager.IsValidClassID(classID) {
|
||||
t.Errorf("Expected class ID %d to be valid", classID)
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid class IDs
|
||||
invalidClasses := []int8{-1, -10, 58, 99, 127}
|
||||
for _, classID := range invalidClasses {
|
||||
if manager.IsValidClassID(classID) {
|
||||
t.Errorf("Expected class ID %d to be invalid", classID)
|
||||
}
|
||||
}
|
||||
|
||||
// Test boundary conditions
|
||||
if !manager.IsValidClassID(MinClassID) {
|
||||
t.Errorf("Expected MinClassID (%d) to be valid", MinClassID)
|
||||
}
|
||||
|
||||
if !manager.IsValidClassID(MaxClassID) {
|
||||
t.Errorf("Expected MaxClassID (%d) to be valid", MaxClassID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassTypeChecking(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test adventure classes
|
||||
adventureClasses := []int8{
|
||||
ClassCommoner, ClassFighter, ClassWarrior, ClassGuardian,
|
||||
ClassTemplar, ClassWizard, ClassRanger, ClassBeastlord,
|
||||
ClassChanneler,
|
||||
}
|
||||
|
||||
for _, classID := range adventureClasses {
|
||||
if !manager.IsAdventureClass(classID) {
|
||||
t.Errorf("Expected class ID %d to be an adventure class", classID)
|
||||
}
|
||||
if manager.IsTradeskillClass(classID) {
|
||||
t.Errorf("Expected class ID %d to not be a tradeskill class", classID)
|
||||
}
|
||||
if manager.GetClassType(classID) != ClassTypeAdventure {
|
||||
t.Errorf("Expected class ID %d to have type '%s', got '%s'", classID, ClassTypeAdventure, manager.GetClassType(classID))
|
||||
}
|
||||
}
|
||||
|
||||
// Test tradeskill classes
|
||||
tradeskillClasses := []int8{
|
||||
ClassArtisan, ClassCraftsman, ClassProvisioner,
|
||||
ClassOutfitter, ClassArmorer, ClassScholar,
|
||||
ClassJeweler, ClassAlchemist,
|
||||
}
|
||||
|
||||
for _, classID := range tradeskillClasses {
|
||||
if manager.IsAdventureClass(classID) {
|
||||
t.Errorf("Expected class ID %d to not be an adventure class", classID)
|
||||
}
|
||||
if !manager.IsTradeskillClass(classID) {
|
||||
t.Errorf("Expected class ID %d to be a tradeskill class", classID)
|
||||
}
|
||||
if manager.GetClassType(classID) != ClassTypeTradeskill {
|
||||
t.Errorf("Expected class ID %d to have type '%s', got '%s'", classID, ClassTypeTradeskill, manager.GetClassType(classID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassHierarchy(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test Guardian hierarchy: Guardian -> Warrior -> Fighter -> Commoner
|
||||
guardianHierarchy := manager.GetClassHierarchy(ClassGuardian)
|
||||
expectedGuardian := []int8{ClassGuardian, ClassWarrior, ClassFighter, ClassCommoner}
|
||||
|
||||
if len(guardianHierarchy) != len(expectedGuardian) {
|
||||
t.Errorf("Guardian hierarchy length: expected %d, got %d", len(expectedGuardian), len(guardianHierarchy))
|
||||
} else {
|
||||
for i, expected := range expectedGuardian {
|
||||
if i < len(guardianHierarchy) && guardianHierarchy[i] != expected {
|
||||
t.Errorf("Guardian hierarchy[%d]: expected %d, got %d", i, expected, guardianHierarchy[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test Templar hierarchy: Templar -> Cleric -> Priest -> Commoner
|
||||
templarHierarchy := manager.GetClassHierarchy(ClassTemplar)
|
||||
expectedTemplar := []int8{ClassTemplar, ClassCleric, ClassPriest, ClassCommoner}
|
||||
|
||||
if len(templarHierarchy) != len(expectedTemplar) {
|
||||
t.Errorf("Templar hierarchy length: expected %d, got %d", len(expectedTemplar), len(templarHierarchy))
|
||||
} else {
|
||||
for i, expected := range expectedTemplar {
|
||||
if i < len(templarHierarchy) && templarHierarchy[i] != expected {
|
||||
t.Errorf("Templar hierarchy[%d]: expected %d, got %d", i, expected, templarHierarchy[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test Commoner hierarchy: just Commoner
|
||||
commonerHierarchy := manager.GetClassHierarchy(ClassCommoner)
|
||||
if len(commonerHierarchy) != 1 || commonerHierarchy[0] != ClassCommoner {
|
||||
t.Errorf("Commoner hierarchy: expected [%d], got %v", ClassCommoner, commonerHierarchy)
|
||||
}
|
||||
|
||||
// Test invalid class
|
||||
invalidHierarchy := manager.GetClassHierarchy(int8(99))
|
||||
if invalidHierarchy != nil {
|
||||
t.Errorf("Expected nil hierarchy for invalid class, got %v", invalidHierarchy)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArchetypeOperations(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test same archetype checking
|
||||
if !manager.IsSameArchetype(ClassWarrior, ClassGuardian) {
|
||||
t.Error("Expected Warrior and Guardian to be same archetype (Fighter)")
|
||||
}
|
||||
|
||||
if !manager.IsSameArchetype(ClassTemplar, ClassMystic) {
|
||||
t.Error("Expected Templar and Mystic to be same archetype (Priest)")
|
||||
}
|
||||
|
||||
if manager.IsSameArchetype(ClassWarrior, ClassTemplar) {
|
||||
t.Error("Expected Warrior and Templar to be different archetypes")
|
||||
}
|
||||
|
||||
// Test archetype class listing
|
||||
fighterClasses := manager.GetArchetypeClasses(ClassFighter)
|
||||
expectedFighterCount := 10 // Including base Fighter class (Fighter, Warrior, Guardian, Berserker, Brawler, Monk, Bruiser, Crusader, Shadowknight, Paladin)
|
||||
if len(fighterClasses) != expectedFighterCount {
|
||||
t.Errorf("Expected %d Fighter archetype classes, got %d", expectedFighterCount, len(fighterClasses))
|
||||
}
|
||||
|
||||
// Verify Fighter is in the list
|
||||
foundFighter := false
|
||||
for _, classID := range fighterClasses {
|
||||
if classID == ClassFighter {
|
||||
foundFighter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundFighter {
|
||||
t.Error("Expected Fighter base class to be in Fighter archetype list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTradeskillClassOperations(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Test tradeskill base class operations (C++ API compatibility)
|
||||
// Note: These functions use specific C++ logic with offsets
|
||||
|
||||
// Test with adventure class (should return as-is for most)
|
||||
warriorTS := manager.GetTSBaseClass(ClassWarrior)
|
||||
if warriorTS != ClassWarrior {
|
||||
t.Errorf("Expected GetTSBaseClass(%d) to return %d, got %d", ClassWarrior, ClassWarrior, warriorTS)
|
||||
}
|
||||
|
||||
// Test secondary tradeskill operations
|
||||
warriorSecondaryTS := manager.GetSecondaryTSBaseClass(ClassWarrior)
|
||||
// This should follow the C++ logic with +42 offset calculations
|
||||
t.Logf("GetSecondaryTSBaseClass(ClassWarrior) = %d", warriorSecondaryTS)
|
||||
}
|
||||
|
||||
func TestManagerStatistics(t *testing.T) {
|
||||
manager := NewManager()
|
||||
|
||||
// Perform some operations to generate statistics
|
||||
manager.GetClassID("WARRIOR")
|
||||
manager.GetClassName(ClassTemplar)
|
||||
manager.GetClassNameCase(ClassRanger)
|
||||
manager.GetBaseClass(ClassGuardian)
|
||||
manager.GetClass(ClassBeastlord)
|
||||
manager.GetClassID("INVALID_CLASS") // This should increment cache misses
|
||||
|
||||
stats := manager.GetStatistics()
|
||||
|
||||
// Check basic statistics
|
||||
if stats["classes_loaded"].(int32) == 0 {
|
||||
t.Error("Expected classes to be loaded")
|
||||
}
|
||||
|
||||
if stats["lookups_performed"].(int32) == 0 {
|
||||
t.Error("Expected lookups to be performed")
|
||||
}
|
||||
|
||||
if stats["total_classes"].(int32) != MaxClasses {
|
||||
t.Errorf("Expected %d total classes, got %d", MaxClasses, stats["total_classes"])
|
||||
}
|
||||
|
||||
// Check adventure vs tradeskill counts
|
||||
adventureCount := stats["adventure_classes"].(int32)
|
||||
tradeskillCount := stats["tradeskill_classes"].(int32)
|
||||
|
||||
if adventureCount == 0 {
|
||||
t.Error("Expected some adventure classes")
|
||||
}
|
||||
|
||||
if tradeskillCount == 0 {
|
||||
t.Error("Expected some tradeskill classes")
|
||||
}
|
||||
|
||||
t.Logf("Statistics: %+v", stats)
|
||||
}
|
||||
|
||||
func TestGlobalFunctions(t *testing.T) {
|
||||
// Test global functions that should initialize manager automatically
|
||||
|
||||
// Test basic operations
|
||||
id := GetClassID("WARRIOR")
|
||||
if id != ClassWarrior {
|
||||
t.Errorf("GetClassID('WARRIOR'): expected %d, got %d", ClassWarrior, id)
|
||||
}
|
||||
|
||||
name := GetClassName(ClassTemplar)
|
||||
if name != "TEMPLAR" {
|
||||
t.Errorf("GetClassName(%d): expected 'TEMPLAR', got '%s'", ClassTemplar, name)
|
||||
}
|
||||
|
||||
displayName := GetClassNameCase(ClassRanger)
|
||||
if displayName != "Ranger" {
|
||||
t.Errorf("GetClassNameCase(%d): expected 'Ranger', got '%s'", ClassRanger, displayName)
|
||||
}
|
||||
|
||||
base := GetBaseClass(ClassGuardian)
|
||||
if base != ClassFighter {
|
||||
t.Errorf("GetBaseClass(%d): expected %d, got %d", ClassGuardian, ClassFighter, base)
|
||||
}
|
||||
|
||||
secondary := GetSecondaryBaseClass(ClassGuardian)
|
||||
if secondary != ClassWarrior {
|
||||
t.Errorf("GetSecondaryBaseClass(%d): expected %d, got %d", ClassGuardian, ClassWarrior, secondary)
|
||||
}
|
||||
|
||||
// Test utility functions
|
||||
if !IsValidClassID(ClassBeastlord) {
|
||||
t.Errorf("Expected ClassBeastlord (%d) to be valid", ClassBeastlord)
|
||||
}
|
||||
|
||||
if !IsAdventureClass(ClassChanneler) {
|
||||
t.Errorf("Expected ClassChanneler (%d) to be adventure class", ClassChanneler)
|
||||
}
|
||||
|
||||
if !IsTradeskillClass(ClassAlchemist) {
|
||||
t.Errorf("Expected ClassAlchemist (%d) to be tradeskill class", ClassAlchemist)
|
||||
}
|
||||
|
||||
classType := GetClassType(ClassWarrior)
|
||||
if classType != ClassTypeAdventure {
|
||||
t.Errorf("Expected ClassWarrior type to be '%s', got '%s'", ClassTypeAdventure, classType)
|
||||
}
|
||||
|
||||
// Test collection functions
|
||||
allClasses := GetAllClasses()
|
||||
if len(allClasses) != MaxClasses {
|
||||
t.Errorf("Expected %d classes in GetAllClasses(), got %d", MaxClasses, len(allClasses))
|
||||
}
|
||||
|
||||
info := GetClassInfo(ClassTemplar)
|
||||
if !info["valid"].(bool) {
|
||||
t.Error("Expected ClassTemplar info to be valid")
|
||||
}
|
||||
|
||||
hierarchy := GetClassHierarchy(ClassGuardian)
|
||||
if len(hierarchy) == 0 {
|
||||
t.Error("Expected non-empty hierarchy for ClassGuardian")
|
||||
}
|
||||
|
||||
if !IsSameArchetype(ClassWarrior, ClassGuardian) {
|
||||
t.Error("Expected Warrior and Guardian to be same archetype")
|
||||
}
|
||||
|
||||
fighterClasses := GetArchetypeClasses(ClassFighter)
|
||||
if len(fighterClasses) == 0 {
|
||||
t.Error("Expected non-empty list of Fighter archetype classes")
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user