From e58aa141a3f75e9b4cdd2414ca16cc13a0756cf6 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Tue, 16 Sep 2025 23:39:20 -0400 Subject: [PATCH] overhauil --- main.go | 831 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 638 insertions(+), 193 deletions(-) diff --git a/main.go b/main.go index c2afd29..57d1b3d 100644 --- a/main.go +++ b/main.go @@ -1,256 +1,701 @@ package main import ( + "encoding/csv" "flag" "fmt" "io" "log" + "math/rand" "os" + "strconv" "strings" + "time" "github.com/araxiaonline/endgame-item-generator/internal/config" "github.com/araxiaonline/endgame-item-generator/internal/db/mysql" - "github.com/araxiaonline/endgame-item-generator/internal/db/sqlite" "github.com/araxiaonline/endgame-item-generator/internal/items" + "github.com/araxiaonline/endgame-item-generator/internal/spells" _ "github.com/go-sql-driver/mysql" "github.com/joho/godotenv" ) -func main() { +// ItemScaler handles generic item scaling operations +type ItemScaler struct { + db *mysql.MySqlDb + debug bool + itemLevel int + quality int + phase int + catchup float64 + spellAssigner *spells.ThematicSpellAssigner + templateManager *items.StatTemplateManager +} +// ScalingResult holds the result of item scaling +type ScalingResult struct { + Item *items.Item + ReferenceItem *items.Item + Success bool + Errors []string + Warnings []string + SpellSQL []string // SQL statements for scaled spells +} + +// InputSource defines how items are provided to the scaler +type InputSource interface { + GetItems() ([]mysql.DbItem, error) + GetName() string +} + +// EntryListSource scales items from a list of entry IDs +type EntryListSource struct { + Entries []int + DB *mysql.MySqlDb +} + +func (e *EntryListSource) GetItems() ([]mysql.DbItem, error) { + var items []mysql.DbItem + for _, entry := range e.Entries { + item, err := e.DB.GetItem(entry) + if err != nil { + log.Printf("Failed to get item %d: %v", entry, err) + continue + } + items = append(items, item) + } + return items, nil +} + +func (e *EntryListSource) GetName() string { + return fmt.Sprintf("Entry List (%d items)", len(e.Entries)) +} + +// CSVSource scales items from a CSV file with entry IDs +type CSVSource struct { + FilePath string + DB *mysql.MySqlDb +} + +func (c *CSVSource) GetItems() ([]mysql.DbItem, error) { + file, err := os.Open(c.FilePath) + if err != nil { + return nil, fmt.Errorf("failed to open CSV file: %v", err) + } + defer file.Close() + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + if err != nil { + return nil, fmt.Errorf("failed to read CSV: %v", err) + } + + var items []mysql.DbItem + for i, record := range records { + if i == 0 && strings.ToLower(record[0]) == "entry" { + continue // Skip header row + } + + if len(record) == 0 { + continue + } + + entry, err := strconv.Atoi(record[0]) + if err != nil { + log.Printf("Invalid entry ID in CSV row %d: %s", i+1, record[0]) + continue + } + + item, err := c.DB.GetItem(entry) + if err != nil { + log.Printf("Failed to get item %d from CSV: %v", entry, err) + continue + } + items = append(items, item) + } + return items, nil +} + +func (c *CSVSource) GetName() string { + return fmt.Sprintf("CSV File: %s", c.FilePath) +} + +// SQLQuerySource scales items from a custom SQL query +type SQLQuerySource struct { + Query string + DB *mysql.MySqlDb +} + +func (s *SQLQuerySource) GetItems() ([]mysql.DbItem, error) { + // For now, use GetRarePlusItems as a fallback since GetItemsByQuery doesn't exist + // This can be extended later to support custom queries + return s.DB.GetRarePlusItems(0, 0) +} + +func (s *SQLQuerySource) GetName() string { + return "Custom SQL Query (using GetRarePlusItems)" +} + +// MapItemsSource scales items from map/boss/gameobject data (like Molten Core) +type MapItemsSource struct { + MapID int + BossEntries []int + GameObjectEntries []int + DB *mysql.MySqlDb +} + +func (m *MapItemsSource) GetItems() ([]mysql.DbItem, error) { + return m.DB.GetBossMapItems(m.MapID, m.BossEntries, m.GameObjectEntries, 0, 0) +} + +func (m *MapItemsSource) GetName() string { + return fmt.Sprintf("Map %d Items", m.MapID) +} + +func NewItemScaler(db *mysql.MySqlDb, debug bool, itemLevel, quality, phase int, catchup float64) *ItemScaler { + return &ItemScaler{ + db: db, + debug: debug, + itemLevel: itemLevel, + quality: quality, + phase: phase, + catchup: catchup, + spellAssigner: spells.NewThematicSpellAssigner(db), + templateManager: items.NewStatTemplateManager(debug), + } +} + +// ScaleItem performs the core scaling logic extracted from raid-gear/main.go +func (s *ItemScaler) ScaleItem(dbItem mysql.DbItem) ScalingResult { + result := ScalingResult{ + Success: false, + Errors: []string{}, + Warnings: []string{}, + SpellSQL: []string{}, + } + + // Create item from database item + item := items.ItemFromDbItem(dbItem) + + // Set quality if not already epic/legendary + if *item.Quality < 4 { + *item.Quality = s.quality + } + + // Check if item should have a thematic spell assigned + if s.debug { + log.Printf("Checking if %s should have thematic spell assigned", item.Name) + } + + if s.shouldAssignThematicSpell(item) { + if s.debug { + log.Printf("Item %s qualifies for thematic spell assignment", item.Name) + } + err := s.assignThematicSpell(&item) + if err != nil { + if s.debug { + log.Printf("Failed to assign thematic spell to %s: %v", item.Name, err) + } + } else { + if s.debug { + log.Printf("Successfully assigned thematic spell to %s", item.Name) + } + } + } else { + if s.debug { + log.Printf("Item %s does not qualify for thematic spell assignment", item.Name) + } + } + + // Get item class type for reference item matching + classType := item.GetClassUserType() + + if s.debug { + log.Printf("Scaling item: %s (Entry: %d) - Class: %d, Subclass: %d, ClassType: %d", + item.Name, item.Entry, *item.Class, *item.Subclass, classType) + } + + var referenceItem items.Item + + if s.phase == 0 { + // Phase 0: Use simple stat templates instead of complex reference matching + s.templateManager.ApplySimpleStatTemplate(&item) + if s.debug { + log.Printf("Using simple stat template for phase 0") + } + } else { + // Phase 1+: Find reference items and use reference item scaling + referenceItems, err := s.findReferenceItems(&item) + if err != nil { + result.Errors = append(result.Errors, fmt.Sprintf("Failed to find reference items: %v", err)) + return result + } + + if len(referenceItems) == 0 { + result.Errors = append(result.Errors, "No compatible reference items found") + result.Warnings = append(result.Warnings, "Manual review required - no reference items available") + return result + } + + // Select random reference item + referenceItem = referenceItems[rand.Intn(len(referenceItems))] + result.ReferenceItem = &referenceItem + + // Apply stats from reference item + item.ApplyStats(referenceItem) + } + item.ScaleItemWithPhase(s.itemLevel, s.quality, s.phase) + + // Apply tier modifiers if phase is specified + if s.phase > 0 || s.catchup != 1.0 { + item.ApplyTierModifiersWithCatchup(s.phase, s.catchup) + } + + // Collect spell SQL for any scaled spells + for _, spell := range item.Spells { + if spell.Scaled { + spellSQL := spells.SpellToSql(spell, s.quality) + result.SpellSQL = append(result.SpellSQL, spellSQL) + if s.debug { + log.Printf("Generated SQL for scaled spell: %s (ID: %d)", spell.Name, spell.ID) + } + } + } + + if s.debug { + if s.phase == 0 { + log.Printf("Successfully scaled %s using simple stat template", item.Name) + } else { + log.Printf("Successfully scaled %s using reference item %s", item.Name, referenceItem.Name) + } + } + + result.Item = &item + result.Success = true + return result +} + +// shouldAssignThematicSpell determines if an item should get a thematic spell +func (s *ItemScaler) shouldAssignThematicSpell(item items.Item) bool { + if s.debug { + log.Printf("DEBUG: shouldAssignThematicSpell - checking item %s", item.Name) + log.Printf("DEBUG: SpellId1: %v, SpellId2: %v, SpellId3: %v", + item.SpellId1, item.SpellId2, item.SpellId3) + } + + // Check if item already has spells + if item.SpellId1 != nil && *item.SpellId1 != 0 { + if s.debug { + log.Printf("DEBUG: Item already has spell in slot 1: %d", *item.SpellId1) + } + return false + } + if item.SpellId2 != nil && *item.SpellId2 != 0 { + if s.debug { + log.Printf("DEBUG: Item already has spell in slot 2: %d", *item.SpellId2) + } + return false + } + if item.SpellId3 != nil && *item.SpellId3 != 0 { + if s.debug { + log.Printf("DEBUG: Item already has spell in slot 3: %d", *item.SpellId3) + } + return false + } + + if s.debug { + log.Printf("DEBUG: Item has no existing spells, checking if it should have a proc") + } + + // Use the thematic spell assigner to determine eligibility + return s.spellAssigner.ShouldHaveProc(*item.Class, *item.Subclass, *item.ItemLevel, *item.Quality, item.Name) +} + +// assignThematicSpell assigns a thematic spell to an item +func (s *ItemScaler) assignThematicSpell(item *items.Item) error { + spellId, err := s.spellAssigner.AssignThematicSpell(item.Entry, *item.Class, *item.Subclass, *item.ItemLevel, *item.Quality, item.Name) + if err != nil { + return err + } + + // Assign to first available spell slot + if item.SpellId1 == nil || *item.SpellId1 == 0 { + *item.SpellId1 = spellId + *item.SpellTrigger1 = 2 // Chance on hit + if s.debug { + log.Printf("Assigned spell %d to %s in slot 1", spellId, item.Name) + } + } else if item.SpellId2 == nil || *item.SpellId2 == 0 { + *item.SpellId2 = spellId + *item.SpellTrigger2 = 2 // Chance on hit + if s.debug { + log.Printf("Assigned spell %d to %s in slot 2", spellId, item.Name) + } + } else if item.SpellId3 == nil || *item.SpellId3 == 0 { + *item.SpellId3 = spellId + *item.SpellTrigger3 = 2 // Chance on hit + if s.debug { + log.Printf("Assigned spell %d to %s in slot 3", spellId, item.Name) + } + } + + return nil +} + +// findReferenceItems uses the sophisticated reference matching logic from raid-gear +func (s *ItemScaler) findReferenceItems(item *items.Item) ([]items.Item, error) { + classType := item.GetClassUserType() + + // Handle subclass mapping for weapons (from raid-gear logic) + subclassToUse := *item.Subclass + if *item.Subclass == 8 { + subclassToUse = 1 // two handed axe instead of sword + } + + // Get high-level reference items + highLevelItems, err := s.db.GetRaidPhase1Items(*item.Class, subclassToUse, 0, 0) + if err != nil { + return nil, fmt.Errorf("failed to get reference items: %v", err) + } + + // Filter items by class type AND inventory type compatibility + var compatibleChoices []items.Item + var classOnlyChoices []items.Item + var anyWeaponChoices []items.Item + + for _, highLevelItem := range highLevelItems { + refItem := items.ItemFromDbItem(highLevelItem) + refItem.ScaleItem(*refItem.ItemLevel, *item.Quality) + refClassType := refItem.GetClassUserType() + + // Check both class type and inventory type compatibility + classMatch := refClassType == classType + invMatch := (item.InventoryType != nil && refItem.InventoryType != nil && + *item.InventoryType == *refItem.InventoryType) + + if classMatch && invMatch { + // Perfect match - both class type and inventory type + compatibleChoices = append(compatibleChoices, refItem) + } else if classMatch { + // Class type match only - good fallback + classOnlyChoices = append(classOnlyChoices, refItem) + } else if *item.Class == 2 && *refItem.Class == 2 { + // Any weapon as last resort for weapons + anyWeaponChoices = append(anyWeaponChoices, refItem) + } + } + + if s.debug { + log.Printf("Reference items found - Perfect: %d, Class-only: %d, Any-weapon: %d", + len(compatibleChoices), len(classOnlyChoices), len(anyWeaponChoices)) + } + + // Return best available match + if len(compatibleChoices) > 0 { + return compatibleChoices, nil + } else if len(classOnlyChoices) > 0 { + if s.debug { + log.Printf("Using class-only match for %s", item.Name) + } + return classOnlyChoices, nil + } else if len(anyWeaponChoices) > 0 { + if s.debug { + log.Printf("Using any-weapon fallback for %s", item.Name) + } + return anyWeaponChoices, nil + } + + return compatibleChoices, nil +} + +// printScalingComparison shows before/after comparison +func printScalingComparison(original, scaled *items.Item, reference *items.Item) { + fmt.Printf("\n=== ITEM SCALING: %s (Entry: %d) ===\n", original.Name, original.Entry) + if reference != nil { + fmt.Printf("Reference Item: %s (Entry: %d)\n", reference.Name, reference.Entry) + } + + // Show key stats comparison + fmt.Printf("Item Level: %d → %d\n", getItemLevel(original), getItemLevel(scaled)) + fmt.Printf("Quality: %d → %d\n", *original.Quality, *scaled.Quality) + + // Show stat changes + originalStats := extractStats(original) + scaledStats := extractStats(scaled) + + fmt.Printf("Stats Changes:\n") + allStats := make(map[int]bool) + for stat := range originalStats { + allStats[stat] = true + } + for stat := range scaledStats { + allStats[stat] = true + } + + for statType := range allStats { + originalValue := originalStats[statType] + scaledValue := scaledStats[statType] + + if originalValue != scaledValue { + statName := getStatName(statType) + fmt.Printf(" %s: %d → %d\n", statName, originalValue, scaledValue) + } + } + fmt.Printf("=====================================\n\n") +} + +// Helper functions +func getItemLevel(item *items.Item) int { + if item.ItemLevel != nil { + return *item.ItemLevel + } + return 0 +} + +func extractStats(item *items.Item) map[int]int { + stats := make(map[int]int) + for i := 1; i <= 8; i++ { + statType := getStatType(item, i) + statValue := getStatValue(item, i) + if statType != nil && statValue != nil && *statType > 0 && *statValue > 0 { + stats[*statType] = *statValue + } + } + return stats +} + +func getStatType(item *items.Item, index int) *int { + switch index { + case 1: + return item.StatType1 + case 2: + return item.StatType2 + case 3: + return item.StatType3 + case 4: + return item.StatType4 + case 5: + return item.StatType5 + case 6: + return item.StatType6 + case 7: + return item.StatType7 + case 8: + return item.StatType8 + default: + return nil + } +} + +func getStatValue(item *items.Item, index int) *int { + switch index { + case 1: + return item.StatValue1 + case 2: + return item.StatValue2 + case 3: + return item.StatValue3 + case 4: + return item.StatValue4 + case 5: + return item.StatValue5 + case 6: + return item.StatValue6 + case 7: + return item.StatValue7 + case 8: + return item.StatValue8 + default: + return nil + } +} + +func getStatName(statType int) string { + if name, exists := config.StatModifierNames[statType]; exists { + return name + } + return fmt.Sprintf("Unknown(%d)", statType) +} + +func parseIntList(s string) []int { + if s == "" { + return []int{} + } + + parts := strings.Split(s, ",") + var result []int + for _, part := range parts { + if num, err := strconv.Atoi(strings.TrimSpace(part)); err == nil { + result = append(result, num) + } + } + return result +} + +func main() { + rand.Seed(time.Now().UnixNano()) log.SetFlags(log.LstdFlags | log.Lshortfile) godotenv.Load() - // database.models.Connect() - debug := flag.Bool("debug", false, "Enable verbose logging inside generator") - difficulty := flag.Int("difficulty", 3, "set the difficulty of the dungeon, defaults to 3 (mythic) 4 (legendary) 5 (ascendant)") - // levelUp := flag.Bool("levelUp", false, "Boss items require higher +1 level to equip, defaults to false") - baselevel := flag.Int("baselevel", 80, "set the base level for items to be used, defaults to 80 this is required for levelUp flag") + // Command line flags + debug := flag.Bool("debug", false, "Enable verbose logging") + itemLevel := flag.Int("item-level", 325, "Target item level for scaling") + quality := flag.Int("quality", 4, "Target quality (4=Epic, 5=Legendary)") + phase := flag.Int("phase", 0, "Tier phase for modifiers (0=no tier bonus, 1-5=tier levels)") + catchup := flag.Float64("catchup", 1.0, "Catchup bonus multiplier (1.0=no bonus, 1.5=50% bonus)") + baseLevel := flag.Int("base-level", 80, "Base level requirement for items") + difficulty := flag.Int("difficulty", 3, "Difficulty level (3=Mythic, 4=Legendary, 5=Ascendant)") + outputSQL := flag.Bool("sql", false, "Output SQL statements") + + // Input source flags + entries := flag.String("entries", "", "Comma-separated list of entry IDs") + csvFile := flag.String("csv", "", "CSV file with entry IDs") + sqlQuery := flag.String("query", "", "Custom SQL query to get items") + mapID := flag.Int("map-id", 0, "Map ID for boss/gameobject items") + bossEntries := flag.String("boss-entries", "", "Comma-separated boss entry IDs") + gameObjectEntries := flag.String("gameobject-entries", "", "Comma-separated gameobject entry IDs") + flag.Parse() - if difficulty == nil || *difficulty < 3 || *difficulty > 5 { - log.Fatal("difficulty must be between 3-5") - os.Exit(1) - } - - if baselevel == nil || *baselevel < 0 { - log.Fatal("base level must be greater than 80") - os.Exit(1) - } - - var itemLevel *int = new(int) - switch *difficulty { - case 3: - *itemLevel = config.MythicItemLevelStart - case 4: - *itemLevel = config.LegendaryItemLevelStart - case 5: - *itemLevel = config.AscendantItemLevelStart - } - if *debug { log.SetOutput(os.Stdout) } else { log.SetOutput(io.Discard) } - // Connect to Mysql + // Connect to database mysqlDb, err := mysql.Connect(&mysql.MySqlConfig{ Host: os.Getenv("DB_HOST"), User: os.Getenv("DB_USER"), Password: os.Getenv("DB_PASSWORD"), Database: os.Getenv("DB_NAME"), }) - if err != nil { - log.Fatal(err) + log.Fatal("Failed to connect to database:", err) } - // Connect to SqlList for EndGame Mapping - sqliteDb, err := sqlite.Connect("./data/items.db") - if err != nil { - log.Fatal(err) + // Determine input source + var source InputSource + sourceCount := 0 + + if *entries != "" { + entryList := parseIntList(*entries) + source = &EntryListSource{Entries: entryList, DB: mysqlDb} + sourceCount++ } - // Get all rare items int the acore_world.item_template that are rare or higher quality - rareItems, err := mysqlDb.GetRarePlusItems(0, 0) - if err != nil { - log.Fatal(err) + if *csvFile != "" { + source = &CSVSource{FilePath: *csvFile, DB: mysqlDb} + sourceCount++ } - // do scaling and write sql for all items that are processed from the rareItems list - for itr, dbItem := range rareItems { + if *sqlQuery != "" { + source = &SQLQuerySource{Query: *sqlQuery, DB: mysqlDb} + sourceCount++ + } - // convert from a dbModel item to Item entity - item := items.ItemFromDbItem(dbItem) + if *mapID > 0 { + source = &MapItemsSource{ + MapID: *mapID, + BossEntries: parseIntList(*bossEntries), + GameObjectEntries: parseIntList(*gameObjectEntries), + DB: mysqlDb, + } + sourceCount++ + } - // the lookup Item is a check to see if the item comes from a dungeon on higher difficulties (4,5) we only process dungeon items - lookupItem, err := sqliteDb.GetItemFromDungeon(item.Entry) + if sourceCount == 0 { + fmt.Println("Error: No input source specified. Use one of:") + fmt.Println(" --entries \"1234,5678\"") + fmt.Println(" --csv items.csv") + fmt.Println(" --query \"SELECT * FROM item_template WHERE ...\"") + fmt.Println(" --map-id 409 --boss-entries \"11502\" --gameobject-entries \"179703\"") + os.Exit(1) + } + + if sourceCount > 1 { + fmt.Println("Error: Only one input source can be specified at a time") + os.Exit(1) + } + + // Initialize scaler + scaler := NewItemScaler(mysqlDb, *debug, *itemLevel, *quality, *phase, *catchup) + + // Get items from source + dbItems, err := source.GetItems() + if err != nil { + log.Fatal("Failed to get items from source:", err) + } + + fmt.Printf("🔧 Generic Item Scaler\n") + fmt.Printf("Source: %s\n", source.GetName()) + fmt.Printf("Target: Item Level %d, Quality %d, Phase %d, Catchup %.1fx\n", *itemLevel, *quality, *phase, *catchup) + fmt.Printf("Processing %d items...\n\n", len(dbItems)) + + // Initialize SQL output if requested + var sqlFile *os.File + if *outputSQL { + sqlFile, err = os.Create("scaled_items.sql") if err != nil { - if !strings.Contains(err.Error(), "no rows in result set") { - log.Printf("failed to lookup item %v from dungeon: %v", item.Entry, err) - } - } - log.Printf("Lookup %v", lookupItem) - // skip items not from a dungeon on higher difficulties - if *difficulty > 3 { - if lookupItem.Entry == 0 { - log.Printf("Item %v Entry: %v is not from a dungeon\n", item.Name, item.Entry) - continue - } else { - log.Printf("Item %v Entry: %v is from a dungeon\n", item.Name, item.Entry) - } + log.Fatal("Failed to create SQL output file:", err) } + defer sqlFile.Close() - // if it is a rare item then we need to scale it up to epic - if *item.Quality < 5 { - *item.Quality = 4 - } + sqlFile.WriteString("-- Generic Item Scaler Output\n") + sqlFile.WriteString(fmt.Sprintf("-- Generated: %s\n", time.Now().Format("2006-01-02 15:04:05"))) + sqlFile.WriteString(fmt.Sprintf("-- Item Level: %d, Quality: %d, Phase: %d, Catchup: %.1fx\n\n", *itemLevel, *quality, *phase, *catchup)) + } - statsList, err := item.GetStatList() - if err != nil { - log.Print(err) - continue - } + // Process items + successCount := 0 + for i, dbItem := range dbItems { + fmt.Printf("[%d/%d] Processing: %s (Entry: %d)\n", i+1, len(dbItems), dbItem.Name, dbItem.Entry) - log.Printf("Item: %v Entry: %v StatsList: %v\n", item.Name, item.Entry, statsList) + // Store original for comparison + originalItem := items.ItemFromDbItem(dbItem) - var highLevelItem mysql.DbItem - if *difficulty == 3 { - rndItem, err := sqliteDb.GetRandItem(*item.Class, *item.Subclass, statsList, false) - if err != nil { - log.Print(err) - continue - } + result := scaler.ScaleItem(dbItem) - if rndItem == (sqlite.HighLevelItem{}) { - log.Fatalf("Failed to get random item for %v Entry: %v\n", item.Name, item.Entry) - } + if result.Success { + successCount++ + fmt.Printf("✅ Successfully scaled %s\n", result.Item.Name) - log.Printf("Random Item: %v Entry: %v\n", rndItem.Name, rndItem.Entry) + // Show comparison + printScalingComparison(&originalItem, result.Item, result.ReferenceItem) - // Take the high level item that has been selected for stats and remap to current item - highLevelItem, err = mysqlDb.GetItem(rndItem.Entry) - if err != nil { - log.Fatal(err) - continue - } - } else { + // Output SQL if requested + if *outputSQL && sqlFile != nil { + // Write item SQL + sqlStatement := items.ItemToSql(*result.Item, *baseLevel, *difficulty, true) + sqlFile.WriteString(sqlStatement + "\n") - highLevelItem, err = mysqlDb.GetByNameAndDifficulty(item.Name, *difficulty-1) - if err != nil { - log.Println(err) - continue - } - } - - // difficulty is used to tweak things in the scaling proces specifically modifiers so stats are not inflated twice by quality multiples - item.SetDifficulty(*difficulty) - - // if the item is not from a dungeon and we made it here, then just scale to mythic which can be used for weekly loot chests or new recipes. - if lookupItem.Entry == 0 { - Scale(highLevelItem, &item, *itemLevel, *item.Quality) - fmt.Print(items.ItemToSql(item, *baselevel, *difficulty, false)) - continue - } - - // if the item is from a dungeon and not a boss item - if lookupItem.CreatureId == 0 { - - if lookupItem.DungeonLevel < 60 && lookupItem.Expansion == 0 { - Scale(highLevelItem, &item, *itemLevel+5, *item.Quality) - fmt.Print(items.ItemToSql(item, *baselevel, *difficulty, false)) - } - - if lookupItem.DungeonLevel == 60 && lookupItem.Expansion == 0 { - Scale(highLevelItem, &item, *itemLevel+10, *item.Quality) - fmt.Print(items.ItemToSql(item, *baselevel, *difficulty, false)) - } - - if lookupItem.DungeonLevel < 70 && lookupItem.Expansion == 1 { - Scale(highLevelItem, &item, *itemLevel+7, *item.Quality) - fmt.Print(items.ItemToSql(item, *baselevel, *difficulty, false)) - } - - if lookupItem.DungeonLevel == 70 && lookupItem.Expansion == 1 { - Scale(highLevelItem, &item, *itemLevel+10, *item.Quality) - fmt.Print(items.ItemToSql(item, *baselevel, *difficulty, false)) - } - - if lookupItem.DungeonLevel < 80 && lookupItem.Expansion == 2 { - Scale(highLevelItem, &item, *itemLevel+7, *item.Quality) - fmt.Print(items.ItemToSql(item, *baselevel, *difficulty, false)) - } - - if lookupItem.DungeonLevel == 80 && lookupItem.Expansion == 2 { - Scale(highLevelItem, &item, *itemLevel+10, *item.Quality) - fmt.Print(items.ItemToSql(item, *baselevel+2, *difficulty, false)) - } - } else { - - var finalBonus int = 0 - var quality int = 4 - - // adjust qualities and levels required based on power and difficulty - if mysql.IsFinalBoss(lookupItem.CreatureId) { - fmt.Printf("-- Final Boss Item: %v Entry: %v difficulty %v\n", item.Name, item.Entry, *difficulty) - finalBonus = 5 - - if *difficulty >= 4 { - quality = 5 + // Write spell SQL for any scaled spells + for _, spellSQL := range result.SpellSQL { + sqlFile.WriteString(spellSQL + "\n") } } - - var reqLevel int - if *difficulty == 4 || *difficulty == 5 { - reqLevel = *baselevel + 5 - } else { - reqLevel = *baselevel + 2 - } - - // if the item is from a boss fight - if lookupItem.DungeonLevel < 60 && lookupItem.Expansion == 0 { - Scale(highLevelItem, &item, *itemLevel+9+finalBonus, quality) - fmt.Print(items.ItemToSql(item, reqLevel-1, *difficulty, false)) - } - - if lookupItem.DungeonLevel == 60 && lookupItem.Expansion == 0 { - Scale(highLevelItem, &item, *itemLevel+23+finalBonus, quality) - fmt.Print(items.ItemToSql(item, reqLevel, *difficulty, false)) - } - - if lookupItem.DungeonLevel < 70 && lookupItem.Expansion == 1 { - Scale(highLevelItem, &item, *itemLevel+10+finalBonus, quality) - fmt.Print(items.ItemToSql(item, reqLevel-1, *difficulty, false)) - } - - if lookupItem.DungeonLevel == 70 && lookupItem.Expansion == 1 { - Scale(highLevelItem, &item, *itemLevel+23+finalBonus, quality) - fmt.Print(items.ItemToSql(item, reqLevel, *difficulty, false)) - } - - if lookupItem.DungeonLevel < 80 && lookupItem.Expansion == 2 { - Scale(highLevelItem, &item, *itemLevel+12+finalBonus, quality) - fmt.Print(items.ItemToSql(item, reqLevel-1, *difficulty, false)) - } - - if lookupItem.DungeonLevel == 80 && lookupItem.Expansion == 2 { - Scale(highLevelItem, &item, *itemLevel+25+finalBonus, quality) - fmt.Print(items.ItemToSql(item, reqLevel, *difficulty, false)) + } else { + fmt.Printf("❌ Failed to scale %s\n", dbItem.Name) + for _, errMsg := range result.Errors { + fmt.Printf(" Error: %s\n", errMsg) } } - fmt.Printf("\n -- Item Updated: %v Entry: %v\n", item.Name, item.Entry) - if itr >= 300 { - // os.Exit(0) + for _, warning := range result.Warnings { + fmt.Printf("⚠️ Warning: %s\n", warning) } + fmt.Println() + } + + // Print summary + fmt.Printf("🏆 Scaling Summary:\n") + fmt.Printf("Total Items: %d\n", len(dbItems)) + fmt.Printf("Successful: %d\n", successCount) + fmt.Printf("Failed: %d\n", len(dbItems)-successCount) + fmt.Printf("Success Rate: %.1f%%\n", float64(successCount)/float64(len(dbItems))*100) + + if *outputSQL { + fmt.Printf("SQL Output: scaled_items.sql\n") } } - -func Scale(highLevelItem mysql.DbItem, item *items.Item, itemLevel, quality int) { - item.ApplyStats(items.ItemFromDbItem(highLevelItem)) - item.ScaleItem(itemLevel, quality) - log.Printf("Item Name: %v Stat1: %v Stat2: %v Stat3: %v Stat4: %v Stat5: %v Stat6: %v Stat7: %v Stat8: %v \n", - item.Name, *item.StatValue1, *item.StatValue2, *item.StatValue3, *item.StatValue4, *item.StatValue5, *item.StatValue6, *item.StatValue7, *item.StatValue8) -}