mirror of
https://github.com/araxiaonline/wow-item-generator.git
synced 2026-06-13 03:02:22 -04:00
overhauil
This commit is contained in:
841
main.go
841
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")
|
||||
// Determine input source
|
||||
var source InputSource
|
||||
sourceCount := 0
|
||||
|
||||
if *entries != "" {
|
||||
entryList := parseIntList(*entries)
|
||||
source = &EntryListSource{Entries: entryList, DB: mysqlDb}
|
||||
sourceCount++
|
||||
}
|
||||
|
||||
if *csvFile != "" {
|
||||
source = &CSVSource{FilePath: *csvFile, DB: mysqlDb}
|
||||
sourceCount++
|
||||
}
|
||||
|
||||
if *sqlQuery != "" {
|
||||
source = &SQLQuerySource{Query: *sqlQuery, DB: mysqlDb}
|
||||
sourceCount++
|
||||
}
|
||||
|
||||
if *mapID > 0 {
|
||||
source = &MapItemsSource{
|
||||
MapID: *mapID,
|
||||
BossEntries: parseIntList(*bossEntries),
|
||||
GameObjectEntries: parseIntList(*gameObjectEntries),
|
||||
DB: mysqlDb,
|
||||
}
|
||||
sourceCount++
|
||||
}
|
||||
|
||||
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(err)
|
||||
log.Fatal("Failed to get items from source:", err)
|
||||
}
|
||||
|
||||
// Get all rare items int the acore_world.item_template that are rare or higher quality
|
||||
rareItems, err := mysqlDb.GetRarePlusItems(0, 0)
|
||||
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 {
|
||||
log.Fatal(err)
|
||||
log.Fatal("Failed to create SQL output file:", err)
|
||||
}
|
||||
defer sqlFile.Close()
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// do scaling and write sql for all items that are processed from the rareItems list
|
||||
for itr, dbItem := range rareItems {
|
||||
// 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)
|
||||
|
||||
// convert from a dbModel item to Item entity
|
||||
item := items.ItemFromDbItem(dbItem)
|
||||
// Store original for comparison
|
||||
originalItem := items.ItemFromDbItem(dbItem)
|
||||
|
||||
// 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 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)
|
||||
result := scaler.ScaleItem(dbItem)
|
||||
|
||||
if result.Success {
|
||||
successCount++
|
||||
fmt.Printf("✅ Successfully scaled %s\n", result.Item.Name)
|
||||
|
||||
// Show comparison
|
||||
printScalingComparison(&originalItem, result.Item, result.ReferenceItem)
|
||||
|
||||
// Output SQL if requested
|
||||
if *outputSQL && sqlFile != nil {
|
||||
// Write item SQL
|
||||
sqlStatement := items.ItemToSql(*result.Item, *baseLevel, *difficulty, true)
|
||||
sqlFile.WriteString(sqlStatement + "\n")
|
||||
|
||||
// Write spell SQL for any scaled spells
|
||||
for _, spellSQL := range result.SpellSQL {
|
||||
sqlFile.WriteString(spellSQL + "\n")
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// if it is a rare item then we need to scale it up to epic
|
||||
if *item.Quality < 5 {
|
||||
*item.Quality = 4
|
||||
}
|
||||
|
||||
statsList, err := item.GetStatList()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Item: %v Entry: %v StatsList: %v\n", item.Name, item.Entry, statsList)
|
||||
|
||||
var highLevelItem mysql.DbItem
|
||||
if *difficulty == 3 {
|
||||
rndItem, err := sqliteDb.GetRandItem(*item.Class, *item.Subclass, statsList, false)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if rndItem == (sqlite.HighLevelItem{}) {
|
||||
log.Fatalf("Failed to get random item for %v Entry: %v\n", item.Name, item.Entry)
|
||||
}
|
||||
|
||||
log.Printf("Random Item: %v Entry: %v\n", rndItem.Name, rndItem.Entry)
|
||||
|
||||
// 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 {
|
||||
|
||||
highLevelItem, err = mysqlDb.GetByNameAndDifficulty(item.Name, *difficulty-1)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
fmt.Printf("❌ Failed to scale %s\n", dbItem.Name)
|
||||
for _, errMsg := range result.Errors {
|
||||
fmt.Printf(" Error: %s\n", errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
for _, warning := range result.Warnings {
|
||||
fmt.Printf("⚠️ Warning: %s\n", warning)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// if the item is from a dungeon and not a boss item
|
||||
if lookupItem.CreatureId == 0 {
|
||||
// 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 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
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n -- Item Updated: %v Entry: %v\n", item.Name, item.Entry)
|
||||
if itr >= 300 {
|
||||
// os.Exit(0)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user