fix factions
This commit is contained in:
parent
6cad1bd9f9
commit
27e720e703
@ -1,6 +1,7 @@
|
||||
package factions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -543,3 +544,293 @@ func BenchmarkScalability(b *testing.B) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark bespoke MasterList features
|
||||
func BenchmarkMasterListBespokeFeatures(b *testing.B) {
|
||||
// Setup function for consistent test data
|
||||
setupMasterList := func() *MasterList {
|
||||
mfl := NewMasterList()
|
||||
|
||||
// Add factions across different types
|
||||
types := []string{"City", "Guild", "Religion", "Race", "Organization"}
|
||||
|
||||
for i := 1; i <= 1000; i++ {
|
||||
factionType := types[i%len(types)]
|
||||
faction := NewFaction(int32(i), fmt.Sprintf("Faction%d", i), factionType, "Benchmark test")
|
||||
mfl.AddFaction(faction)
|
||||
}
|
||||
return mfl
|
||||
}
|
||||
|
||||
b.Run("GetFactionSafe", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
factionID := int32((i % 1000) + 1)
|
||||
_, _ = mfl.GetFactionSafe(factionID)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetFactionByName", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
names := []string{"faction1", "faction50", "faction100", "faction500", "faction1000"}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
name := names[i%len(names)]
|
||||
_ = mfl.GetFactionByName(name)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetFactionsByType", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
types := []string{"City", "Guild", "Religion", "Race", "Organization"}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
factionType := types[i%len(types)]
|
||||
_ = mfl.GetFactionsByType(factionType)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetSpecialFactions", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
// Add some special factions
|
||||
for i := int32(1); i <= 5; i++ {
|
||||
faction := NewFaction(i, fmt.Sprintf("Special%d", i), "Special", "Special faction")
|
||||
mfl.AddFaction(faction)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetSpecialFactions()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetRegularFactions", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetRegularFactions()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetTypes", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetTypes()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetAllFactionsList", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetAllFactionsList()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetFactionIDs", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetFactionIDs()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("UpdateFaction", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
factionID := int32((i % 1000) + 1)
|
||||
updatedFaction := &Faction{
|
||||
ID: factionID,
|
||||
Name: fmt.Sprintf("Updated%d", i),
|
||||
Type: "Updated",
|
||||
Description: "Updated faction",
|
||||
}
|
||||
mfl.UpdateFaction(updatedFaction)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("ForEach", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
count := 0
|
||||
mfl.ForEach(func(id int32, faction *Faction) {
|
||||
count++
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetStatistics", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetStatistics()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("RemoveFaction", func(b *testing.B) {
|
||||
b.StopTimer()
|
||||
mfl := setupMasterList()
|
||||
initialCount := mfl.GetFactionCount()
|
||||
|
||||
// Pre-populate with factions we'll remove
|
||||
for i := 0; i < b.N; i++ {
|
||||
faction := NewFaction(int32(20000+i), fmt.Sprintf("ToRemove%d", i), "Temporary", "Temporary faction")
|
||||
mfl.AddFaction(faction)
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mfl.RemoveFaction(int32(20000 + i))
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
if mfl.GetFactionCount() != initialCount {
|
||||
b.Errorf("Expected %d factions after removal, got %d", initialCount, mfl.GetFactionCount())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Memory allocation benchmarks for bespoke features
|
||||
func BenchmarkMasterListBespokeFeatures_Allocs(b *testing.B) {
|
||||
setupMasterList := func() *MasterList {
|
||||
mfl := NewMasterList()
|
||||
types := []string{"City", "Guild", "Religion", "Race", "Organization"}
|
||||
|
||||
for i := 1; i <= 100; i++ {
|
||||
factionType := types[i%len(types)]
|
||||
faction := NewFaction(int32(i), fmt.Sprintf("Faction%d", i), factionType, "Benchmark test")
|
||||
mfl.AddFaction(faction)
|
||||
}
|
||||
return mfl
|
||||
}
|
||||
|
||||
b.Run("GetFactionByName_Allocs", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetFactionByName("faction1")
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetFactionsByType_Allocs", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetFactionsByType("City")
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetTypes_Allocs", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetTypes()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetSpecialFactions_Allocs", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
// Add some special factions
|
||||
for i := int32(1); i <= 5; i++ {
|
||||
faction := NewFaction(i, fmt.Sprintf("Special%d", i), "Special", "Special faction")
|
||||
mfl.AddFaction(faction)
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetSpecialFactions()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("GetStatistics_Allocs", func(b *testing.B) {
|
||||
mfl := setupMasterList()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mfl.GetStatistics()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Concurrent benchmarks for bespoke features
|
||||
func BenchmarkMasterListBespokeConcurrent(b *testing.B) {
|
||||
b.Run("ConcurrentReads", func(b *testing.B) {
|
||||
mfl := NewMasterList()
|
||||
|
||||
// Setup test data
|
||||
types := []string{"City", "Guild", "Religion", "Race", "Organization"}
|
||||
for i := 1; i <= 100; i++ {
|
||||
factionType := types[i%len(types)]
|
||||
faction := NewFaction(int32(i), fmt.Sprintf("Faction%d", i), factionType, "Benchmark test")
|
||||
mfl.AddFaction(faction)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
// Mix of read operations
|
||||
switch i % 6 {
|
||||
case 0:
|
||||
mfl.GetFaction(int32(i%100 + 1))
|
||||
case 1:
|
||||
mfl.GetFactionsByType("City")
|
||||
case 2:
|
||||
mfl.GetFactionByName("faction1")
|
||||
case 3:
|
||||
mfl.GetSpecialFactions()
|
||||
case 4:
|
||||
mfl.GetRegularFactions()
|
||||
case 5:
|
||||
mfl.GetTypes()
|
||||
}
|
||||
i++
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ConcurrentMixed", func(b *testing.B) {
|
||||
mfl := NewMasterList()
|
||||
|
||||
// Setup test data
|
||||
types := []string{"City", "Guild", "Religion", "Race", "Organization"}
|
||||
for i := 1; i <= 100; i++ {
|
||||
factionType := types[i%len(types)]
|
||||
faction := NewFaction(int32(i), fmt.Sprintf("Faction%d", i), factionType, "Benchmark test")
|
||||
mfl.AddFaction(faction)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
// Mix of read and write operations (mostly reads)
|
||||
switch i % 10 {
|
||||
case 0: // 10% writes
|
||||
faction := NewFaction(int32(i+50000), fmt.Sprintf("Concurrent%d", i), "Concurrent", "Concurrent test")
|
||||
mfl.AddFaction(faction)
|
||||
default: // 90% reads
|
||||
switch i % 5 {
|
||||
case 0:
|
||||
mfl.GetFaction(int32(i%100 + 1))
|
||||
case 1:
|
||||
mfl.GetFactionsByType("City")
|
||||
case 2:
|
||||
mfl.GetFactionByName("faction1")
|
||||
case 3:
|
||||
mfl.GetSpecialFactions()
|
||||
case 4:
|
||||
mfl.GetTypes()
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -174,3 +174,177 @@ func TestFactionValidation(t *testing.T) {
|
||||
t.Error("Expected error when adding faction with empty name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMasterListBespokeFeatures(t *testing.T) {
|
||||
mfl := NewMasterList()
|
||||
|
||||
// Create test factions with different properties
|
||||
faction1 := NewFaction(1, "Special Faction", "Special", "A special faction")
|
||||
faction2 := NewFaction(20, "City Faction", "City", "A city faction")
|
||||
faction3 := NewFaction(21, "Guild Faction", "Guild", "A guild faction")
|
||||
faction4 := NewFaction(30, "Another City", "City", "Another city faction")
|
||||
|
||||
// Add factions
|
||||
mfl.AddFaction(faction1)
|
||||
mfl.AddFaction(faction2)
|
||||
mfl.AddFaction(faction3)
|
||||
mfl.AddFaction(faction4)
|
||||
|
||||
// Test GetFactionSafe
|
||||
retrieved, exists := mfl.GetFactionSafe(20)
|
||||
if !exists || retrieved == nil {
|
||||
t.Error("GetFactionSafe should return existing faction and true")
|
||||
}
|
||||
|
||||
_, exists = mfl.GetFactionSafe(9999)
|
||||
if exists {
|
||||
t.Error("GetFactionSafe should return false for non-existent ID")
|
||||
}
|
||||
|
||||
// Test GetFactionByName (case-insensitive)
|
||||
found := mfl.GetFactionByName("city faction")
|
||||
if found == nil || found.ID != 20 {
|
||||
t.Error("GetFactionByName should find 'City Faction' (case insensitive)")
|
||||
}
|
||||
|
||||
found = mfl.GetFactionByName("GUILD FACTION")
|
||||
if found == nil || found.ID != 21 {
|
||||
t.Error("GetFactionByName should find 'Guild Faction' (uppercase)")
|
||||
}
|
||||
|
||||
found = mfl.GetFactionByName("NonExistent")
|
||||
if found != nil {
|
||||
t.Error("GetFactionByName should return nil for non-existent faction")
|
||||
}
|
||||
|
||||
// Test GetFactionsByType
|
||||
cityFactions := mfl.GetFactionsByType("City")
|
||||
if len(cityFactions) != 2 {
|
||||
t.Errorf("GetFactionsByType('City') returned %v results, want 2", len(cityFactions))
|
||||
}
|
||||
|
||||
guildFactions := mfl.GetFactionsByType("Guild")
|
||||
if len(guildFactions) != 1 {
|
||||
t.Errorf("GetFactionsByType('Guild') returned %v results, want 1", len(guildFactions))
|
||||
}
|
||||
|
||||
// Test GetSpecialFactions and GetRegularFactions
|
||||
specialFactions := mfl.GetSpecialFactions()
|
||||
if len(specialFactions) != 1 {
|
||||
t.Errorf("GetSpecialFactions() returned %v results, want 1", len(specialFactions))
|
||||
}
|
||||
|
||||
regularFactions := mfl.GetRegularFactions()
|
||||
if len(regularFactions) != 3 {
|
||||
t.Errorf("GetRegularFactions() returned %v results, want 3", len(regularFactions))
|
||||
}
|
||||
|
||||
// Test GetTypes
|
||||
types := mfl.GetTypes()
|
||||
if len(types) < 3 {
|
||||
t.Errorf("GetTypes() returned %v types, want at least 3", len(types))
|
||||
}
|
||||
|
||||
// Verify types contains expected values
|
||||
typeMap := make(map[string]bool)
|
||||
for _, factionType := range types {
|
||||
typeMap[factionType] = true
|
||||
}
|
||||
if !typeMap["Special"] || !typeMap["City"] || !typeMap["Guild"] {
|
||||
t.Error("GetTypes() should contain 'Special', 'City', and 'Guild'")
|
||||
}
|
||||
|
||||
// Test UpdateFaction
|
||||
updatedFaction := &Faction{
|
||||
ID: 20,
|
||||
Name: "Updated City Faction",
|
||||
Type: "UpdatedCity",
|
||||
Description: "An updated city faction",
|
||||
}
|
||||
|
||||
err := mfl.UpdateFaction(updatedFaction)
|
||||
if err != nil {
|
||||
t.Errorf("UpdateFaction failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify the update worked
|
||||
retrieved = mfl.GetFaction(20)
|
||||
if retrieved.Name != "Updated City Faction" {
|
||||
t.Errorf("Expected updated name 'Updated City Faction', got '%s'", retrieved.Name)
|
||||
}
|
||||
|
||||
if retrieved.Type != "UpdatedCity" {
|
||||
t.Errorf("Expected updated type 'UpdatedCity', got '%s'", retrieved.Type)
|
||||
}
|
||||
|
||||
// Test updating non-existent faction
|
||||
nonExistentFaction := &Faction{ID: 9999, Name: "Non-existent"}
|
||||
err = mfl.UpdateFaction(nonExistentFaction)
|
||||
if err == nil {
|
||||
t.Error("UpdateFaction should fail for non-existent faction")
|
||||
}
|
||||
|
||||
// Test GetAllFactionsList
|
||||
allList := mfl.GetAllFactionsList()
|
||||
if len(allList) != 4 {
|
||||
t.Errorf("GetAllFactionsList() returned %v factions, want 4", len(allList))
|
||||
}
|
||||
|
||||
// Test GetFactionIDs
|
||||
ids := mfl.GetFactionIDs()
|
||||
if len(ids) != 4 {
|
||||
t.Errorf("GetFactionIDs() returned %v IDs, want 4", len(ids))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMasterListConcurrency(t *testing.T) {
|
||||
mfl := NewMasterList()
|
||||
|
||||
// Add initial factions
|
||||
for i := 1; i <= 50; i++ {
|
||||
faction := NewFaction(int32(i+100), "Faction", "Test", "Test faction")
|
||||
mfl.AddFaction(faction)
|
||||
}
|
||||
|
||||
// Test concurrent access
|
||||
done := make(chan bool, 10)
|
||||
|
||||
// Concurrent readers
|
||||
for i := 0; i < 5; i++ {
|
||||
go func() {
|
||||
defer func() { done <- true }()
|
||||
for j := 0; j < 100; j++ {
|
||||
mfl.GetFaction(int32(j%50 + 101))
|
||||
mfl.GetFactionsByType("Test")
|
||||
mfl.GetFactionByName("faction")
|
||||
mfl.HasFaction(int32(j%50 + 101))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Concurrent writers
|
||||
for i := 0; i < 5; i++ {
|
||||
go func(workerID int) {
|
||||
defer func() { done <- true }()
|
||||
for j := 0; j < 10; j++ {
|
||||
factionID := int32(workerID*1000 + j + 1000)
|
||||
faction := NewFaction(factionID, "Worker Faction", "Worker", "Worker test faction")
|
||||
mfl.AddFaction(faction) // Some may fail due to concurrent additions
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all goroutines
|
||||
for i := 0; i < 10; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
// Verify final state - should have at least 50 initial factions
|
||||
finalCount := mfl.GetFactionCount()
|
||||
if finalCount < 50 {
|
||||
t.Errorf("Expected at least 50 factions after concurrent operations, got %d", finalCount)
|
||||
}
|
||||
if finalCount > 100 {
|
||||
t.Errorf("Expected at most 100 factions after concurrent operations, got %d", finalCount)
|
||||
}
|
||||
}
|
||||
|
@ -2,31 +2,125 @@ package factions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"eq2emu/internal/common"
|
||||
"eq2emu/internal/database"
|
||||
)
|
||||
|
||||
// MasterList manages all factions using the generic MasterList base
|
||||
// MasterList is a specialized faction master list optimized for:
|
||||
// - Fast ID-based lookups (O(1))
|
||||
// - Fast name-based lookups (O(1))
|
||||
// - Fast type-based filtering (indexed)
|
||||
// - Efficient faction relationships management
|
||||
// - Special faction handling
|
||||
// - Value range queries and validation
|
||||
type MasterList struct {
|
||||
*common.MasterList[int32, *Faction]
|
||||
factionNameList map[string]*Faction // Factions by name lookup
|
||||
// Core storage
|
||||
factions map[int32]*Faction // ID -> Faction
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Specialized indices for O(1) lookups
|
||||
byName map[string]*Faction // Lowercase name -> faction
|
||||
byType map[string][]*Faction // Type -> factions
|
||||
specialFactions map[int32]*Faction // Special factions (ID <= SpecialFactionIDMax)
|
||||
regularFactions map[int32]*Faction // Regular factions (ID > SpecialFactionIDMax)
|
||||
|
||||
// Faction relationships
|
||||
hostileFactions map[int32][]int32 // Hostile faction relationships
|
||||
friendlyFactions map[int32][]int32 // Friendly faction relationships
|
||||
mutex sync.RWMutex // Additional mutex for relationships
|
||||
|
||||
// Cached metadata
|
||||
types []string // Unique types (cached)
|
||||
typeStats map[string]int // Type -> count
|
||||
metaStale bool // Whether metadata cache needs refresh
|
||||
}
|
||||
|
||||
// NewMasterList creates a new master faction list
|
||||
// NewMasterList creates a new specialized faction master list
|
||||
func NewMasterList() *MasterList {
|
||||
return &MasterList{
|
||||
MasterList: common.NewMasterList[int32, *Faction](),
|
||||
factionNameList: make(map[string]*Faction),
|
||||
factions: make(map[int32]*Faction),
|
||||
byName: make(map[string]*Faction),
|
||||
byType: make(map[string][]*Faction),
|
||||
specialFactions: make(map[int32]*Faction),
|
||||
regularFactions: make(map[int32]*Faction),
|
||||
hostileFactions: make(map[int32][]int32),
|
||||
friendlyFactions: make(map[int32][]int32),
|
||||
typeStats: make(map[string]int),
|
||||
metaStale: true,
|
||||
}
|
||||
}
|
||||
|
||||
// AddFaction adds a faction to the master list
|
||||
// refreshMetaCache updates the cached metadata
|
||||
func (ml *MasterList) refreshMetaCache() {
|
||||
if !ml.metaStale {
|
||||
return
|
||||
}
|
||||
|
||||
// Clear and rebuild type stats
|
||||
ml.typeStats = make(map[string]int)
|
||||
typeSet := make(map[string]struct{})
|
||||
|
||||
// Collect unique values and stats
|
||||
for _, faction := range ml.factions {
|
||||
factionType := faction.GetType()
|
||||
if factionType != "" {
|
||||
ml.typeStats[factionType]++
|
||||
typeSet[factionType] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear and rebuild cached slices
|
||||
ml.types = ml.types[:0]
|
||||
for factionType := range typeSet {
|
||||
ml.types = append(ml.types, factionType)
|
||||
}
|
||||
|
||||
ml.metaStale = false
|
||||
}
|
||||
|
||||
// updateFactionIndices updates all indices for a faction
|
||||
func (ml *MasterList) updateFactionIndices(faction *Faction, add bool) {
|
||||
if add {
|
||||
// Add to name index
|
||||
ml.byName[strings.ToLower(faction.GetName())] = faction
|
||||
|
||||
// Add to type index
|
||||
factionType := faction.GetType()
|
||||
if factionType != "" {
|
||||
ml.byType[factionType] = append(ml.byType[factionType], faction)
|
||||
}
|
||||
|
||||
// Add to special/regular index
|
||||
if faction.IsSpecialFaction() {
|
||||
ml.specialFactions[faction.ID] = faction
|
||||
} else {
|
||||
ml.regularFactions[faction.ID] = faction
|
||||
}
|
||||
} else {
|
||||
// Remove from name index
|
||||
delete(ml.byName, strings.ToLower(faction.GetName()))
|
||||
|
||||
// Remove from type index
|
||||
factionType := faction.GetType()
|
||||
if factionType != "" {
|
||||
typeFactionsSlice := ml.byType[factionType]
|
||||
for i, f := range typeFactionsSlice {
|
||||
if f.ID == faction.ID {
|
||||
ml.byType[factionType] = append(typeFactionsSlice[:i], typeFactionsSlice[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from special/regular index
|
||||
delete(ml.specialFactions, faction.ID)
|
||||
delete(ml.regularFactions, faction.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// AddFaction adds a faction with full indexing
|
||||
func (ml *MasterList) AddFaction(faction *Faction) error {
|
||||
if faction == nil {
|
||||
return fmt.Errorf("faction cannot be nil")
|
||||
@ -36,61 +130,79 @@ func (ml *MasterList) AddFaction(faction *Faction) error {
|
||||
return fmt.Errorf("faction is not valid")
|
||||
}
|
||||
|
||||
// Use generic base for main storage
|
||||
if !ml.MasterList.Add(faction) {
|
||||
ml.mutex.Lock()
|
||||
defer ml.mutex.Unlock()
|
||||
|
||||
// Check if exists
|
||||
if _, exists := ml.factions[faction.ID]; exists {
|
||||
return fmt.Errorf("faction with ID %d already exists", faction.ID)
|
||||
}
|
||||
|
||||
// Update name lookup
|
||||
ml.mutex.Lock()
|
||||
ml.factionNameList[faction.Name] = faction
|
||||
ml.mutex.Unlock()
|
||||
// Add to core storage
|
||||
ml.factions[faction.ID] = faction
|
||||
|
||||
// Update all indices
|
||||
ml.updateFactionIndices(faction, true)
|
||||
|
||||
// Invalidate metadata cache
|
||||
ml.metaStale = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFaction returns a faction by ID
|
||||
// GetFaction retrieves by ID (O(1))
|
||||
func (ml *MasterList) GetFaction(id int32) *Faction {
|
||||
return ml.MasterList.Get(id)
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
return ml.factions[id]
|
||||
}
|
||||
|
||||
// GetFactionByName returns a faction by name
|
||||
// GetFactionSafe retrieves a faction by ID with existence check
|
||||
func (ml *MasterList) GetFactionSafe(id int32) (*Faction, bool) {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
faction, exists := ml.factions[id]
|
||||
return faction, exists
|
||||
}
|
||||
|
||||
// GetFactionByName retrieves a faction by name (case-insensitive, O(1))
|
||||
func (ml *MasterList) GetFactionByName(name string) *Faction {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
return ml.factionNameList[name]
|
||||
return ml.byName[strings.ToLower(name)]
|
||||
}
|
||||
|
||||
// HasFaction checks if a faction exists by ID
|
||||
func (ml *MasterList) HasFaction(factionID int32) bool {
|
||||
return ml.MasterList.Exists(factionID)
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
_, exists := ml.factions[factionID]
|
||||
return exists
|
||||
}
|
||||
|
||||
// HasFactionByName checks if a faction exists by name
|
||||
func (ml *MasterList) HasFactionByName(name string) bool {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
_, exists := ml.factionNameList[name]
|
||||
_, exists := ml.byName[strings.ToLower(name)]
|
||||
return exists
|
||||
}
|
||||
|
||||
// RemoveFaction removes a faction by ID
|
||||
// RemoveFaction removes a faction and updates all indices
|
||||
func (ml *MasterList) RemoveFaction(factionID int32) bool {
|
||||
faction := ml.MasterList.Get(factionID)
|
||||
if faction == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove from generic base
|
||||
if !ml.MasterList.Remove(factionID) {
|
||||
return false
|
||||
}
|
||||
|
||||
ml.mutex.Lock()
|
||||
defer ml.mutex.Unlock()
|
||||
|
||||
// Remove from name lookup
|
||||
delete(ml.factionNameList, faction.Name)
|
||||
faction, exists := ml.factions[factionID]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove from core storage
|
||||
delete(ml.factions, factionID)
|
||||
|
||||
// Update all indices
|
||||
ml.updateFactionIndices(faction, false)
|
||||
|
||||
// Remove from relationship maps
|
||||
delete(ml.hostileFactions, factionID)
|
||||
@ -117,10 +229,13 @@ func (ml *MasterList) RemoveFaction(factionID int32) bool {
|
||||
ml.friendlyFactions[id] = newFriendlies
|
||||
}
|
||||
|
||||
// Invalidate metadata cache
|
||||
ml.metaStale = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// UpdateFaction updates an existing faction
|
||||
// UpdateFaction updates an existing faction and refreshes indices
|
||||
func (ml *MasterList) UpdateFaction(faction *Faction) error {
|
||||
if faction == nil {
|
||||
return fmt.Errorf("faction cannot be nil")
|
||||
@ -130,65 +245,153 @@ func (ml *MasterList) UpdateFaction(faction *Faction) error {
|
||||
return fmt.Errorf("faction is not valid")
|
||||
}
|
||||
|
||||
oldFaction := ml.MasterList.Get(faction.ID)
|
||||
if oldFaction == nil {
|
||||
return fmt.Errorf("faction with ID %d does not exist", faction.ID)
|
||||
}
|
||||
|
||||
// Update in generic base
|
||||
if err := ml.MasterList.Update(faction); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ml.mutex.Lock()
|
||||
defer ml.mutex.Unlock()
|
||||
|
||||
// If name changed, update name map
|
||||
if oldFaction.Name != faction.Name {
|
||||
delete(ml.factionNameList, oldFaction.Name)
|
||||
ml.factionNameList[faction.Name] = faction
|
||||
// Check if exists
|
||||
old, exists := ml.factions[faction.ID]
|
||||
if !exists {
|
||||
return fmt.Errorf("faction %d not found", faction.ID)
|
||||
}
|
||||
|
||||
// Remove old faction from indices (but not core storage yet)
|
||||
ml.updateFactionIndices(old, false)
|
||||
|
||||
// Update core storage
|
||||
ml.factions[faction.ID] = faction
|
||||
|
||||
// Add new faction to indices
|
||||
ml.updateFactionIndices(faction, true)
|
||||
|
||||
// Invalidate metadata cache
|
||||
ml.metaStale = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFactionCount returns the total number of factions
|
||||
func (ml *MasterList) GetFactionCount() int32 {
|
||||
return int32(ml.MasterList.Size())
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
return int32(len(ml.factions))
|
||||
}
|
||||
|
||||
// GetAllFactions returns a copy of all factions
|
||||
// GetAllFactions returns a copy of all factions map
|
||||
func (ml *MasterList) GetAllFactions() map[int32]*Faction {
|
||||
return ml.MasterList.GetAll()
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
result := make(map[int32]*Faction, len(ml.factions))
|
||||
maps.Copy(result, ml.factions)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetAllFactionsList returns all factions as a slice
|
||||
func (ml *MasterList) GetAllFactionsList() []*Faction {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
|
||||
result := make([]*Faction, 0, len(ml.factions))
|
||||
for _, faction := range ml.factions {
|
||||
result = append(result, faction)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetFactionIDs returns all faction IDs
|
||||
func (ml *MasterList) GetFactionIDs() []int32 {
|
||||
return ml.MasterList.GetAllIDs()
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
|
||||
result := make([]int32, 0, len(ml.factions))
|
||||
for id := range ml.factions {
|
||||
result = append(result, id)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetFactionsByType returns all factions of a specific type
|
||||
// GetFactionsByType returns all factions of a specific type (O(1))
|
||||
func (ml *MasterList) GetFactionsByType(factionType string) []*Faction {
|
||||
return ml.MasterList.Filter(func(f *Faction) bool {
|
||||
return f.Type == factionType
|
||||
})
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
return ml.byType[factionType]
|
||||
}
|
||||
|
||||
// GetSpecialFactions returns all special factions (ID <= SpecialFactionIDMax)
|
||||
func (ml *MasterList) GetSpecialFactions() map[int32]*Faction {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
result := make(map[int32]*Faction, len(ml.specialFactions))
|
||||
maps.Copy(result, ml.specialFactions)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetRegularFactions returns all regular factions (ID > SpecialFactionIDMax)
|
||||
func (ml *MasterList) GetRegularFactions() map[int32]*Faction {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
result := make(map[int32]*Faction, len(ml.regularFactions))
|
||||
maps.Copy(result, ml.regularFactions)
|
||||
return result
|
||||
}
|
||||
|
||||
// Size returns the total number of factions
|
||||
func (ml *MasterList) Size() int {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
return len(ml.factions)
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the master list is empty
|
||||
func (ml *MasterList) IsEmpty() bool {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
return len(ml.factions) == 0
|
||||
}
|
||||
|
||||
// Clear removes all factions and relationships
|
||||
func (ml *MasterList) Clear() {
|
||||
ml.MasterList.Clear()
|
||||
|
||||
ml.mutex.Lock()
|
||||
defer ml.mutex.Unlock()
|
||||
|
||||
ml.factionNameList = make(map[string]*Faction)
|
||||
// Clear all maps
|
||||
ml.factions = make(map[int32]*Faction)
|
||||
ml.byName = make(map[string]*Faction)
|
||||
ml.byType = make(map[string][]*Faction)
|
||||
ml.specialFactions = make(map[int32]*Faction)
|
||||
ml.regularFactions = make(map[int32]*Faction)
|
||||
ml.hostileFactions = make(map[int32][]int32)
|
||||
ml.friendlyFactions = make(map[int32][]int32)
|
||||
|
||||
// Clear cached metadata
|
||||
ml.types = ml.types[:0]
|
||||
ml.typeStats = make(map[string]int)
|
||||
ml.metaStale = true
|
||||
}
|
||||
|
||||
// GetTypes returns all unique faction types using cached results
|
||||
func (ml *MasterList) GetTypes() []string {
|
||||
ml.mutex.Lock() // Need write lock to potentially update cache
|
||||
defer ml.mutex.Unlock()
|
||||
|
||||
ml.refreshMetaCache()
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
result := make([]string, len(ml.types))
|
||||
copy(result, ml.types)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetDefaultFactionValue returns the default value for a faction
|
||||
func (ml *MasterList) GetDefaultFactionValue(factionID int32) int32 {
|
||||
faction := ml.MasterList.Get(factionID)
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
faction := ml.factions[factionID]
|
||||
if faction != nil {
|
||||
return faction.DefaultValue
|
||||
}
|
||||
@ -197,7 +400,9 @@ func (ml *MasterList) GetDefaultFactionValue(factionID int32) int32 {
|
||||
|
||||
// GetIncreaseAmount returns the default increase amount for a faction
|
||||
func (ml *MasterList) GetIncreaseAmount(factionID int32) int32 {
|
||||
faction := ml.MasterList.Get(factionID)
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
faction := ml.factions[factionID]
|
||||
if faction != nil {
|
||||
return int32(faction.PositiveChange)
|
||||
}
|
||||
@ -206,7 +411,9 @@ func (ml *MasterList) GetIncreaseAmount(factionID int32) int32 {
|
||||
|
||||
// GetDecreaseAmount returns the default decrease amount for a faction
|
||||
func (ml *MasterList) GetDecreaseAmount(factionID int32) int32 {
|
||||
faction := ml.MasterList.Get(factionID)
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
faction := ml.factions[factionID]
|
||||
if faction != nil {
|
||||
return int32(faction.NegativeChange)
|
||||
}
|
||||
@ -216,7 +423,9 @@ func (ml *MasterList) GetDecreaseAmount(factionID int32) int32 {
|
||||
// GetFactionNameByID returns the faction name for a given ID
|
||||
func (ml *MasterList) GetFactionNameByID(factionID int32) string {
|
||||
if factionID > 0 {
|
||||
faction := ml.MasterList.Get(factionID)
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
faction := ml.factions[factionID]
|
||||
if faction != nil {
|
||||
return faction.Name
|
||||
}
|
||||
@ -271,102 +480,110 @@ func (ml *MasterList) ValidateFactions() []string {
|
||||
|
||||
var issues []string
|
||||
|
||||
// Use WithReadLock to avoid copying the entire map
|
||||
var seenIDs map[int32]*Faction
|
||||
ml.MasterList.WithReadLock(func(allFactions map[int32]*Faction) {
|
||||
seenIDs = make(map[int32]*Faction, len(allFactions))
|
||||
|
||||
// Pass 1: Validate main faction list and build seenID map
|
||||
for id, faction := range allFactions {
|
||||
// Pass 1: Validate main faction list
|
||||
for id, faction := range ml.factions {
|
||||
if faction == nil {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
issues = append(issues, fmt.Sprintf("Faction ID %d is nil", id))
|
||||
continue
|
||||
}
|
||||
|
||||
if faction.ID <= 0 || faction.Name == "" {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
issues = append(issues, fmt.Sprintf("Faction ID %d is invalid or unnamed", id))
|
||||
}
|
||||
|
||||
if faction.ID != id {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
issues = append(issues, fmt.Sprintf("Faction ID mismatch: map key %d != faction ID %d", id, faction.ID))
|
||||
}
|
||||
|
||||
seenIDs[id] = faction
|
||||
}
|
||||
})
|
||||
|
||||
// Pass 2: Validate factionNameList
|
||||
for name, faction := range ml.factionNameList {
|
||||
// Pass 2: Validate byName index
|
||||
for name, faction := range ml.byName {
|
||||
if faction == nil {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
issues = append(issues, fmt.Sprintf("Faction name '%s' maps to nil", name))
|
||||
continue
|
||||
}
|
||||
|
||||
if faction.Name != name {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
issues = append(issues, fmt.Sprintf("Faction name mismatch: map key '%s' != faction name '%s'", name, faction.Name))
|
||||
if strings.ToLower(faction.Name) != name {
|
||||
issues = append(issues, fmt.Sprintf("Faction name index mismatch: map key '%s' != lowercase faction name '%s'", name, strings.ToLower(faction.Name)))
|
||||
}
|
||||
|
||||
if _, ok := seenIDs[faction.ID]; !ok {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
issues = append(issues, fmt.Sprintf("Faction '%s' (ID %d) exists in name map but not in ID map", name, faction.ID))
|
||||
if _, ok := ml.factions[faction.ID]; !ok {
|
||||
issues = append(issues, fmt.Sprintf("Faction '%s' (ID %d) exists in name index but not in main storage", faction.Name, faction.ID))
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3: Validate relationships using prebuilt seenIDs
|
||||
// Pass 3: Validate byType index
|
||||
for factionType, factions := range ml.byType {
|
||||
for _, faction := range factions {
|
||||
if faction == nil {
|
||||
issues = append(issues, fmt.Sprintf("Type '%s' has nil faction", factionType))
|
||||
continue
|
||||
}
|
||||
|
||||
if faction.Type != factionType {
|
||||
issues = append(issues, fmt.Sprintf("Faction %d (type '%s') found in wrong type index '%s'", faction.ID, faction.Type, factionType))
|
||||
}
|
||||
|
||||
if _, ok := ml.factions[faction.ID]; !ok {
|
||||
issues = append(issues, fmt.Sprintf("Faction %d exists in type index but not in main storage", faction.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 4: Validate special/regular faction indices
|
||||
for id, faction := range ml.specialFactions {
|
||||
if faction == nil {
|
||||
issues = append(issues, fmt.Sprintf("Special faction ID %d is nil", id))
|
||||
continue
|
||||
}
|
||||
|
||||
if !faction.IsSpecialFaction() {
|
||||
issues = append(issues, fmt.Sprintf("Faction %d is in special index but is not special (ID > %d)", id, SpecialFactionIDMax))
|
||||
}
|
||||
|
||||
if _, ok := ml.factions[id]; !ok {
|
||||
issues = append(issues, fmt.Sprintf("Special faction %d exists in special index but not in main storage", id))
|
||||
}
|
||||
}
|
||||
|
||||
for id, faction := range ml.regularFactions {
|
||||
if faction == nil {
|
||||
issues = append(issues, fmt.Sprintf("Regular faction ID %d is nil", id))
|
||||
continue
|
||||
}
|
||||
|
||||
if faction.IsSpecialFaction() {
|
||||
issues = append(issues, fmt.Sprintf("Faction %d is in regular index but is special (ID <= %d)", id, SpecialFactionIDMax))
|
||||
}
|
||||
|
||||
if _, ok := ml.factions[id]; !ok {
|
||||
issues = append(issues, fmt.Sprintf("Regular faction %d exists in regular index but not in main storage", id))
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 5: Validate relationships
|
||||
for sourceID, targets := range ml.hostileFactions {
|
||||
if _, ok := seenIDs[sourceID]; !ok {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
if _, ok := ml.factions[sourceID]; !ok {
|
||||
issues = append(issues, fmt.Sprintf("Hostile relationship defined for non-existent faction %d", sourceID))
|
||||
}
|
||||
for _, targetID := range targets {
|
||||
if _, ok := seenIDs[targetID]; !ok {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
issues = append(issues, fmt.Sprintf("Faction %d has Hostile relationship with non-existent faction %d", sourceID, targetID))
|
||||
if _, ok := ml.factions[targetID]; !ok {
|
||||
issues = append(issues, fmt.Sprintf("Faction %d has hostile relationship with non-existent faction %d", sourceID, targetID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for sourceID, targets := range ml.friendlyFactions {
|
||||
if _, ok := seenIDs[sourceID]; !ok {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
if _, ok := ml.factions[sourceID]; !ok {
|
||||
issues = append(issues, fmt.Sprintf("Friendly relationship defined for non-existent faction %d", sourceID))
|
||||
}
|
||||
for _, targetID := range targets {
|
||||
if _, ok := seenIDs[targetID]; !ok {
|
||||
if issues == nil {
|
||||
issues = make([]string, 0, 10)
|
||||
}
|
||||
issues = append(issues, fmt.Sprintf("Faction %d has Friendly relationship with non-existent faction %d", sourceID, targetID))
|
||||
if _, ok := ml.factions[targetID]; !ok {
|
||||
issues = append(issues, fmt.Sprintf("Faction %d has friendly relationship with non-existent faction %d", sourceID, targetID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if issues == nil {
|
||||
return []string{}
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
@ -375,3 +592,139 @@ func (ml *MasterList) IsValid() bool {
|
||||
issues := ml.ValidateFactions()
|
||||
return len(issues) == 0
|
||||
}
|
||||
|
||||
// ForEach executes a function for each faction
|
||||
func (ml *MasterList) ForEach(fn func(int32, *Faction)) {
|
||||
ml.mutex.RLock()
|
||||
defer ml.mutex.RUnlock()
|
||||
|
||||
for id, faction := range ml.factions {
|
||||
fn(id, faction)
|
||||
}
|
||||
}
|
||||
|
||||
// GetStatistics returns statistics about the faction system using cached data
|
||||
func (ml *MasterList) GetStatistics() map[string]any {
|
||||
ml.mutex.Lock() // Need write lock to potentially update cache
|
||||
defer ml.mutex.Unlock()
|
||||
|
||||
ml.refreshMetaCache()
|
||||
|
||||
stats := make(map[string]any)
|
||||
stats["total_factions"] = len(ml.factions)
|
||||
|
||||
if len(ml.factions) == 0 {
|
||||
return stats
|
||||
}
|
||||
|
||||
// Use cached type stats
|
||||
stats["factions_by_type"] = ml.typeStats
|
||||
|
||||
// Calculate additional stats
|
||||
var specialCount, regularCount int
|
||||
var minID, maxID int32
|
||||
var minDefaultValue, maxDefaultValue int32 = MaxFactionValue, MinFactionValue
|
||||
var totalPositiveChange, totalNegativeChange int64
|
||||
first := true
|
||||
|
||||
for id, faction := range ml.factions {
|
||||
if faction.IsSpecialFaction() {
|
||||
specialCount++
|
||||
} else {
|
||||
regularCount++
|
||||
}
|
||||
|
||||
if first {
|
||||
minID = id
|
||||
maxID = id
|
||||
minDefaultValue = faction.DefaultValue
|
||||
maxDefaultValue = faction.DefaultValue
|
||||
first = false
|
||||
} else {
|
||||
if id < minID {
|
||||
minID = id
|
||||
}
|
||||
if id > maxID {
|
||||
maxID = id
|
||||
}
|
||||
if faction.DefaultValue < minDefaultValue {
|
||||
minDefaultValue = faction.DefaultValue
|
||||
}
|
||||
if faction.DefaultValue > maxDefaultValue {
|
||||
maxDefaultValue = faction.DefaultValue
|
||||
}
|
||||
}
|
||||
|
||||
totalPositiveChange += int64(faction.PositiveChange)
|
||||
totalNegativeChange += int64(faction.NegativeChange)
|
||||
}
|
||||
|
||||
stats["special_factions"] = specialCount
|
||||
stats["regular_factions"] = regularCount
|
||||
stats["min_id"] = minID
|
||||
stats["max_id"] = maxID
|
||||
stats["id_range"] = maxID - minID
|
||||
stats["min_default_value"] = minDefaultValue
|
||||
stats["max_default_value"] = maxDefaultValue
|
||||
stats["total_positive_change"] = totalPositiveChange
|
||||
stats["total_negative_change"] = totalNegativeChange
|
||||
|
||||
// Relationship stats
|
||||
stats["total_hostile_relationships"] = len(ml.hostileFactions)
|
||||
stats["total_friendly_relationships"] = len(ml.friendlyFactions)
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// LoadAllFactions loads all factions from the database into the master list
|
||||
func (ml *MasterList) LoadAllFactions(db *database.Database) error {
|
||||
if db == nil {
|
||||
return fmt.Errorf("database connection is nil")
|
||||
}
|
||||
|
||||
// Clear existing factions
|
||||
ml.Clear()
|
||||
|
||||
query := `SELECT id, name, type, description, negative_change, positive_change, default_value FROM factions ORDER BY id`
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to query factions: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
count := 0
|
||||
for rows.Next() {
|
||||
faction := &Faction{
|
||||
db: db,
|
||||
isNew: false,
|
||||
}
|
||||
|
||||
err := rows.Scan(&faction.ID, &faction.Name, &faction.Type, &faction.Description,
|
||||
&faction.NegativeChange, &faction.PositiveChange, &faction.DefaultValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan faction: %w", err)
|
||||
}
|
||||
|
||||
if err := ml.AddFaction(faction); err != nil {
|
||||
return fmt.Errorf("failed to add faction %d to master list: %w", faction.ID, err)
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return fmt.Errorf("error iterating faction rows: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAllFactionsFromDatabase is a convenience function that creates a master list and loads all factions
|
||||
func LoadAllFactionsFromDatabase(db *database.Database) (*MasterList, error) {
|
||||
masterList := NewMasterList()
|
||||
err := masterList.LoadAllFactions(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return masterList, nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user