diff --git a/cmd/debug/main.go b/cmd/debug/main.go index 83c6ae3..afb07ee 100644 --- a/cmd/debug/main.go +++ b/cmd/debug/main.go @@ -33,36 +33,62 @@ func main() { debugDir := "testdata/debug" os.MkdirAll(debugDir, 0755) - // Define regions for 1080p (from config.go — calibrated from real screenshot) + // Define regions for 1080p (from config.go — calibrated from botty reference) regions := map[string]image.Rectangle{ - "health_orb": image.Rect(370, 945, 460, 1030), - "mana_orb": image.Rect(1580, 910, 1670, 1000), - "xp_bar": image.Rect(0, 1058, 1920, 1080), - "belt": image.Rect(500, 990, 900, 1040), - "minimap": image.Rect(1600, 0, 1920, 320), - "inventory": image.Rect(960, 330, 1490, 770), - "stash": image.Rect(430, 330, 960, 770), - "skill_left": image.Rect(200, 1030, 250, 1078), - "skill_right": image.Rect(1670, 1030, 1730, 1078), + "health_globe": image.Rect(240, 870, 600, 1080), + "mana_globe": image.Rect(1330, 870, 1690, 1080), + "health_slice": image.Rect(415, 915, 425, 1060), + "mana_slice": image.Rect(1505, 915, 1515, 1060), + "health_orb": image.Rect(240, 870, 600, 1080), + "mana_orb": image.Rect(1330, 870, 1690, 1080), + "xp_bar": image.Rect(0, 1058, 1920, 1080), + "belt": image.Rect(500, 990, 900, 1040), + "minimap": image.Rect(1600, 0, 1920, 320), + "inventory": image.Rect(960, 330, 1490, 770), + "stash": image.Rect(430, 330, 960, 770), + "skill_left": image.Rect(200, 1030, 250, 1078), + "skill_right": image.Rect(1670, 1030, 1730, 1078), } fmt.Println("\n=== REGION ANALYSIS ===") - // Analyze health orb - analyzeRegion(img, "health_orb", regions["health_orb"], config.Colors.HealthFilled, debugDir) + // Analyze health globe (full region) + analyzeRegion(img, "health_globe", regions["health_globe"], config.Colors.HealthFilled, debugDir) - // Analyze mana orb - analyzeRegion(img, "mana_orb", regions["mana_orb"], config.Colors.ManaFilled, debugDir) + // Analyze mana globe (full region) + analyzeRegion(img, "mana_globe", regions["mana_globe"], config.Colors.ManaFilled, debugDir) + + // Analyze slices + analyzeRegion(img, "health_slice", regions["health_slice"], config.Colors.HealthFilled, debugDir) + analyzeRegion(img, "mana_slice", regions["mana_slice"], config.Colors.ManaFilled, debugDir) + + // Use the vertical slice method for accurate orb reading + fmt.Println("\n=== ORB SLICE READING (vertical strip method) ===") + pipeline := vision.NewPipeline(0.5) + healthSliceColor := vision.ColorRange{ + LowerH: config.Colors.HealthFilled.LowerH, LowerS: config.Colors.HealthFilled.LowerS, LowerV: config.Colors.HealthFilled.LowerV, + UpperH: config.Colors.HealthFilled.UpperH, UpperS: config.Colors.HealthFilled.UpperS, UpperV: config.Colors.HealthFilled.UpperV, + } + manaSliceColor := vision.ColorRange{ + LowerH: config.Colors.ManaFilled.LowerH, LowerS: config.Colors.ManaFilled.LowerS, LowerV: config.Colors.ManaFilled.LowerV, + UpperH: config.Colors.ManaFilled.UpperH, UpperS: config.Colors.ManaFilled.UpperS, UpperV: config.Colors.ManaFilled.UpperV, + } + healthPct := pipeline.ReadOrbSlicePercentage(img, regions["health_slice"], healthSliceColor) + manaPct := pipeline.ReadOrbSlicePercentage(img, regions["mana_slice"], manaSliceColor) + fmt.Printf("Health orb (slice): %.1f%%\n", healthPct*100) + fmt.Printf("Mana orb (slice): %.1f%%\n", manaPct*100) // Sample some specific pixels in the orbs for detailed analysis fmt.Println("\n=== PIXEL SAMPLING ===") - samplePixelsInRegion(img, "health_orb", regions["health_orb"]) - samplePixelsInRegion(img, "mana_orb", regions["mana_orb"]) + samplePixelsInRegion(img, "health_globe", regions["health_globe"]) + samplePixelsInRegion(img, "mana_globe", regions["mana_globe"]) + samplePixelsInRegion(img, "health_slice", regions["health_slice"]) + samplePixelsInRegion(img, "mana_slice", regions["mana_slice"]) // Suggest new HSV ranges based on analysis fmt.Println("\n=== RECOMMENDATIONS ===") - recommendHealthRange(img, regions["health_orb"]) - recommendManaRange(img, regions["mana_orb"]) + recommendHealthRange(img, regions["health_globe"]) + recommendManaRange(img, regions["mana_globe"]) fmt.Printf("\nDebug images saved to: %s\n", debugDir) fmt.Println("Run this tool after implementing the fixes to verify detection works correctly.") diff --git a/debug b/debug index 9bc1f2e..d9ada1a 100755 Binary files a/debug and b/debug differ diff --git a/pkg/engine/vision/vision.go b/pkg/engine/vision/vision.go index d31aedf..c156f9a 100644 --- a/pkg/engine/vision/vision.go +++ b/pkg/engine/vision/vision.go @@ -206,6 +206,36 @@ func (p *Pipeline) ReadOrbPercentage(frame image.Image, orbRegion image.Rectangl return float64(filledPixels) / float64(totalPixels) } +// ReadOrbSlicePercentage reads an orb's fill level using a thin vertical slice. +// The orb fills from bottom to top, so we count matching pixels in the slice +// and return the percentage. This is much more accurate than scanning the whole orb. +func (p *Pipeline) ReadOrbSlicePercentage(frame image.Image, sliceRegion image.Rectangle, filledColor ColorRange) float64 { + bounds := sliceRegion.Intersect(frame.Bounds()) + if bounds.Empty() { + return 0.0 + } + + totalPixels := 0 + filledPixels := 0 + + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + c := frame.At(x, y) + hsv := RGBToHSV(c) + totalPixels++ + if p.colorInRange(hsv, filledColor) { + filledPixels++ + } + } + } + + if totalPixels == 0 { + return 0.0 + } + + return float64(filledPixels) / float64(totalPixels) +} + // GetPixelColor returns the color at a specific pixel. func (p *Pipeline) GetPixelColor(frame image.Image, x, y int) color.Color { return frame.At(x, y) @@ -281,10 +311,18 @@ func (p *Pipeline) colorsMatch(c1, c2 color.Color, tolerance int) bool { } // colorInRange checks if HSV color is within range. +// Supports hue wrapping: if UpperH > 360, it wraps around (e.g., 350-370 means 350-360 OR 0-10). func (p *Pipeline) colorInRange(hsv HSV, colorRange ColorRange) bool { - return hsv.H >= colorRange.LowerH && hsv.H <= colorRange.UpperH && - hsv.S >= colorRange.LowerS && hsv.S <= colorRange.UpperS && - hsv.V >= colorRange.LowerV && hsv.V <= colorRange.UpperV + if hsv.S < colorRange.LowerS || hsv.S > colorRange.UpperS || + hsv.V < colorRange.LowerV || hsv.V > colorRange.UpperV { + return false + } + // Handle hue wrapping + if colorRange.UpperH > 360 { + // Wrapping range: e.g., 350-370 means H >= 350 OR H <= 10 + return hsv.H >= colorRange.LowerH || hsv.H <= (colorRange.UpperH-360) + } + return hsv.H >= colorRange.LowerH && hsv.H <= colorRange.UpperH } // floodFill finds connected pixels of the same color. diff --git a/plugins/d2r/config.go b/plugins/d2r/config.go index e724032..8a4d179 100644 --- a/plugins/d2r/config.go +++ b/plugins/d2r/config.go @@ -56,11 +56,12 @@ type Config struct { func DefaultConfig() Config { return Config{ Colors: Colors{ - // Calibrated HSV ranges from real D2R screenshot (testdata/d2r_1080p.png) - // Health orb is bright RED: RGB ~(236,15,20) → HSV H 0-10, S 150+, V 150+ - HealthFilled: HSVRange{0, 150, 150, 10, 255, 255}, - // Mana orb is BLUE: H 200-240, S 100+, V 80+ - ManaFilled: HSVRange{200, 100, 80, 240, 255, 255}, + // Calibrated from botty reference (720p scaled to 1080p) + // Health orb RED: OpenCV H 178-183 → our H 356-366, wraps around 360 + // Using H 350-360 + H 0-10 range (handled by wrapping in colorInRange) + HealthFilled: HSVRange{350, 60, 20, 370, 255, 255}, // H wraps: 350-370 means 350-360,0-10 + // Mana orb BLUE: actual H 228-236 from screenshot + ManaFilled: HSVRange{226, 100, 20, 240, 255, 255}, ItemUnique: HSVRange{15, 100, 180, 30, 255, 255}, ItemSet: HSVRange{35, 100, 150, 55, 255, 255}, ItemRare: HSVRange{15, 50, 200, 25, 150, 255}, @@ -96,15 +97,19 @@ func RegisterProfiles(registry *resolution.Registry) error { Height: 1080, Label: "1080p", Regions: map[string]image.Rectangle{ - "health_orb": image.Rect(370, 945, 460, 1030), - "mana_orb": image.Rect(1580, 910, 1670, 1000), - "xp_bar": image.Rect(0, 1058, 1920, 1080), - "belt": image.Rect(500, 990, 900, 1040), - "minimap": image.Rect(1600, 0, 1920, 320), - "inventory": image.Rect(960, 330, 1490, 770), - "stash": image.Rect(430, 330, 960, 770), - "skill_left": image.Rect(200, 1030, 250, 1078), - "skill_right": image.Rect(1670, 1030, 1730, 1078), + "health_globe": image.Rect(240, 870, 600, 1080), // Full health globe region + "mana_globe": image.Rect(1330, 870, 1690, 1080), // Full mana globe region + "health_slice": image.Rect(415, 915, 425, 1060), // Thin vertical strip through orb center + "mana_slice": image.Rect(1505, 915, 1515, 1060), // Thin vertical strip through orb center + "health_orb": image.Rect(240, 870, 600, 1080), // Alias for backward compat + "mana_orb": image.Rect(1330, 870, 1690, 1080), // Alias for backward compat + "xp_bar": image.Rect(0, 1058, 1920, 1080), + "belt": image.Rect(500, 990, 900, 1040), + "minimap": image.Rect(1600, 0, 1920, 320), + "inventory": image.Rect(960, 330, 1490, 770), + "stash": image.Rect(430, 330, 960, 770), + "skill_left": image.Rect(200, 1030, 250, 1078), + "skill_right": image.Rect(1670, 1030, 1730, 1078), }, }, // 1280x720 (720p) - Secondary resolution (scaled from 1080p) @@ -113,15 +118,19 @@ func RegisterProfiles(registry *resolution.Registry) error { Height: 720, Label: "720p", Regions: map[string]image.Rectangle{ - "health_orb": image.Rect(247, 630, 307, 687), - "mana_orb": image.Rect(1053, 607, 1113, 667), - "xp_bar": image.Rect(0, 705, 1280, 720), - "belt": image.Rect(333, 660, 600, 693), - "minimap": image.Rect(1067, 0, 1280, 213), - "inventory": image.Rect(640, 220, 993, 513), - "stash": image.Rect(287, 220, 640, 513), - "skill_left": image.Rect(133, 687, 167, 718), - "skill_right": image.Rect(1113, 687, 1153, 718), + "health_globe": image.Rect(160, 580, 400, 720), + "mana_globe": image.Rect(887, 580, 1127, 720), + "health_slice": image.Rect(309, 610, 316, 711), + "mana_slice": image.Rect(961, 610, 968, 711), + "health_orb": image.Rect(160, 580, 400, 720), + "mana_orb": image.Rect(887, 580, 1127, 720), + "xp_bar": image.Rect(0, 705, 1280, 720), + "belt": image.Rect(333, 660, 600, 693), + "minimap": image.Rect(1067, 0, 1280, 213), + "inventory": image.Rect(640, 220, 993, 513), + "stash": image.Rect(287, 220, 640, 513), + "skill_left": image.Rect(133, 687, 167, 718), + "skill_right": image.Rect(1113, 687, 1153, 718), }, }, } diff --git a/testdata/debug/debug_health_globe.png b/testdata/debug/debug_health_globe.png new file mode 100644 index 0000000..e74e82e Binary files /dev/null and b/testdata/debug/debug_health_globe.png differ diff --git a/testdata/debug/debug_health_slice.png b/testdata/debug/debug_health_slice.png new file mode 100644 index 0000000..ab2c922 Binary files /dev/null and b/testdata/debug/debug_health_slice.png differ diff --git a/testdata/debug/debug_mana_globe.png b/testdata/debug/debug_mana_globe.png new file mode 100644 index 0000000..8761f69 Binary files /dev/null and b/testdata/debug/debug_mana_globe.png differ diff --git a/testdata/debug/debug_mana_slice.png b/testdata/debug/debug_mana_slice.png new file mode 100644 index 0000000..9360642 Binary files /dev/null and b/testdata/debug/debug_mana_slice.png differ