Add missing cmd/iso-bot files
This commit is contained in:
parent
6a9562c406
commit
4e4acaa78d
2 changed files with 358 additions and 0 deletions
214
cmd/iso-bot/gen_testdata.go
Normal file
214
cmd/iso-bot/gen_testdata.go
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
//go:build ignore
|
||||
|
||||
// Generate test data for development.
|
||||
// Run with: go run gen_testdata.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create testdata directory
|
||||
if err := os.MkdirAll("testdata", 0755); err != nil {
|
||||
log.Fatalf("Failed to create testdata directory: %v", err)
|
||||
}
|
||||
|
||||
// Generate synthetic D2R screenshot
|
||||
img := generateD2RScreenshot()
|
||||
|
||||
// Save as PNG
|
||||
filename := filepath.Join("testdata", "d2r_1080p.png")
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create test image file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := png.Encode(file, img); err != nil {
|
||||
log.Fatalf("Failed to encode test image: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Generated test image: %s", filename)
|
||||
}
|
||||
|
||||
func generateD2RScreenshot() image.Image {
|
||||
// Create 1920x1080 black image
|
||||
img := image.NewRGBA(image.Rect(0, 0, 1920, 1080))
|
||||
|
||||
// Fill with dark background (simulating game background)
|
||||
darkGray := color.RGBA{20, 20, 25, 255}
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{darkGray}, image.Point{}, draw.Src)
|
||||
|
||||
// Add some noise/texture to make it more realistic
|
||||
for y := 0; y < 1080; y += 5 {
|
||||
for x := 0; x < 1920; x += 5 {
|
||||
noise := color.RGBA{
|
||||
uint8(15 + (x+y)%20),
|
||||
uint8(15 + (x+y)%25),
|
||||
uint8(20 + (x+y)%30),
|
||||
255,
|
||||
}
|
||||
fillRect(img, image.Rect(x, y, x+3, y+3), noise)
|
||||
}
|
||||
}
|
||||
|
||||
// Health orb (red circle) - Region: (28, 545, 198, 715)
|
||||
healthRegion := image.Rect(28, 545, 198, 715)
|
||||
healthColor := color.RGBA{220, 20, 20, 255} // Bright red for health
|
||||
drawOrb(img, healthRegion, healthColor, 0.75) // 75% filled
|
||||
|
||||
// Mana orb (blue circle) - Region: (1722, 545, 1892, 715)
|
||||
manaRegion := image.Rect(1722, 545, 1892, 715)
|
||||
manaColor := color.RGBA{20, 20, 220, 255} // Bright blue for mana
|
||||
drawOrb(img, manaRegion, manaColor, 0.60) // 60% filled
|
||||
|
||||
// XP bar - Region: (0, 1058, 1920, 1080)
|
||||
xpRegion := image.Rect(0, 1058, 1920, 1080)
|
||||
xpColor := color.RGBA{255, 215, 0, 255} // Gold color
|
||||
fillRect(img, xpRegion, color.RGBA{40, 40, 40, 255}) // Dark background
|
||||
// Partial XP fill
|
||||
xpFillWidth := int(float64(xpRegion.Dx()) * 0.3) // 30% XP
|
||||
xpFillRegion := image.Rect(xpRegion.Min.X, xpRegion.Min.Y, xpRegion.Min.X+xpFillWidth, xpRegion.Max.Y)
|
||||
fillRect(img, xpFillRegion, xpColor)
|
||||
|
||||
// Belt - Region: (838, 1010, 1082, 1058)
|
||||
beltRegion := image.Rect(838, 1010, 1082, 1058)
|
||||
beltColor := color.RGBA{80, 60, 40, 255} // Brown belt background
|
||||
fillRect(img, beltRegion, beltColor)
|
||||
|
||||
// Add some belt slots
|
||||
slotWidth := beltRegion.Dx() / 4
|
||||
for i := 0; i < 4; i++ {
|
||||
slotX := beltRegion.Min.X + i*slotWidth + 5
|
||||
slotRegion := image.Rect(slotX, beltRegion.Min.Y+5, slotX+slotWidth-10, beltRegion.Max.Y-5)
|
||||
slotColor := color.RGBA{60, 45, 30, 255}
|
||||
fillRect(img, slotRegion, slotColor)
|
||||
|
||||
// Add a potion in first slot
|
||||
if i == 0 {
|
||||
potionColor := color.RGBA{200, 50, 50, 255} // Red potion
|
||||
potionRegion := image.Rect(slotX+5, beltRegion.Min.Y+10, slotX+slotWidth-15, beltRegion.Max.Y-10)
|
||||
fillRect(img, potionRegion, potionColor)
|
||||
}
|
||||
}
|
||||
|
||||
// Minimap - Region: (1600, 0, 1920, 320)
|
||||
minimapRegion := image.Rect(1600, 0, 1920, 320)
|
||||
minimapColor := color.RGBA{40, 40, 50, 255} // Dark blue-gray
|
||||
fillRect(img, minimapRegion, minimapColor)
|
||||
|
||||
// Add some minimap elements
|
||||
// Player dot (center)
|
||||
centerX := minimapRegion.Min.X + minimapRegion.Dx()/2
|
||||
centerY := minimapRegion.Min.Y + minimapRegion.Dy()/2
|
||||
playerDot := image.Rect(centerX-3, centerY-3, centerX+3, centerY+3)
|
||||
fillRect(img, playerDot, color.RGBA{255, 255, 255, 255}) // White player dot
|
||||
|
||||
// Some terrain
|
||||
for i := 0; i < 50; i++ {
|
||||
x := minimapRegion.Min.X + (i*7)%minimapRegion.Dx()
|
||||
y := minimapRegion.Min.Y + (i*11)%minimapRegion.Dy()
|
||||
terrainColor := color.RGBA{60 + uint8(i%30), 80 + uint8(i%20), 40 + uint8(i%25), 255}
|
||||
terrainDot := image.Rect(x, y, x+2, y+2)
|
||||
fillRect(img, terrainDot, terrainColor)
|
||||
}
|
||||
|
||||
// Skills - left and right
|
||||
// Left skill: (194, 1036, 246, 1088)
|
||||
leftSkillRegion := image.Rect(194, 1036, 246, 1088)
|
||||
skillColor := color.RGBA{100, 100, 150, 255}
|
||||
fillRect(img, leftSkillRegion, skillColor)
|
||||
|
||||
// Right skill: (1674, 1036, 1726, 1088)
|
||||
rightSkillRegion := image.Rect(1674, 1036, 1726, 1088)
|
||||
fillRect(img, rightSkillRegion, skillColor)
|
||||
|
||||
// Add some item drops on the ground (colored text rectangles)
|
||||
items := []struct {
|
||||
text string
|
||||
color color.RGBA
|
||||
x, y int
|
||||
}{
|
||||
{"Shako", color.RGBA{255, 165, 0, 255}, 400, 600}, // Orange unique
|
||||
{"Rune", color.RGBA{255, 215, 0, 255}, 500, 650}, // Gold
|
||||
{"Magic Item", color.RGBA{100, 100, 255, 255}, 600, 700}, // Blue magic
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
textRegion := image.Rect(item.x, item.y, item.x+len(item.text)*8, item.y+15)
|
||||
fillRect(img, textRegion, item.color)
|
||||
}
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
// drawOrb draws a circular orb with fill percentage
|
||||
func drawOrb(img *image.RGBA, region image.Rectangle, fillColor color.RGBA, fillPct float64) {
|
||||
centerX := region.Min.X + region.Dx()/2
|
||||
centerY := region.Min.Y + region.Dy()/2
|
||||
radius := region.Dx() / 2
|
||||
|
||||
// Draw orb background (dark)
|
||||
darkColor := color.RGBA{40, 40, 40, 255}
|
||||
fillCircle(img, centerX, centerY, radius, darkColor)
|
||||
|
||||
// Draw border
|
||||
borderColor := color.RGBA{100, 100, 100, 255}
|
||||
drawCircleBorder(img, centerX, centerY, radius, borderColor, 2)
|
||||
|
||||
// Draw filled portion (from bottom up)
|
||||
if fillPct > 0 {
|
||||
fillHeight := int(float64(region.Dy()) * fillPct)
|
||||
fillStartY := region.Max.Y - fillHeight
|
||||
|
||||
// Fill the circle partially
|
||||
for y := fillStartY; y < region.Max.Y; y++ {
|
||||
for x := region.Min.X; x < region.Max.X; x++ {
|
||||
dx := x - centerX
|
||||
dy := y - centerY
|
||||
if dx*dx + dy*dy <= radius*radius {
|
||||
img.Set(x, y, fillColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillCircle fills a circle with the given color
|
||||
func fillCircle(img *image.RGBA, centerX, centerY, radius int, col color.RGBA) {
|
||||
for y := centerY - radius; y <= centerY + radius; y++ {
|
||||
for x := centerX - radius; x <= centerX + radius; x++ {
|
||||
dx := x - centerX
|
||||
dy := y - centerY
|
||||
if dx*dx + dy*dy <= radius*radius {
|
||||
img.Set(x, y, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// drawCircleBorder draws a circle border
|
||||
func drawCircleBorder(img *image.RGBA, centerX, centerY, radius int, col color.RGBA, thickness int) {
|
||||
for y := centerY - radius - thickness; y <= centerY + radius + thickness; y++ {
|
||||
for x := centerX - radius - thickness; x <= centerX + radius + thickness; x++ {
|
||||
dx := x - centerX
|
||||
dy := y - centerY
|
||||
dist := dx*dx + dy*dy
|
||||
if dist <= (radius+thickness)*(radius+thickness) && dist >= (radius-thickness)*(radius-thickness) {
|
||||
img.Set(x, y, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillRect fills a rectangle with the given color
|
||||
func fillRect(img *image.RGBA, rect image.Rectangle, col color.RGBA) {
|
||||
draw.Draw(img, rect, &image.Uniform{col}, image.Point{}, draw.Src)
|
||||
}
|
||||
144
cmd/iso-bot/main.go
Normal file
144
cmd/iso-bot/main.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// iso-bot — isometric game bot engine.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// iso-bot --game d2r --routine mephisto --api :8080
|
||||
// iso-bot --dev --api :8080 --capture-file testdata/d2r_1080p.png
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"git.cloonar.com/openclawd/iso-bot/pkg/api"
|
||||
"git.cloonar.com/openclawd/iso-bot/pkg/engine"
|
||||
"git.cloonar.com/openclawd/iso-bot/pkg/engine/capture/backends"
|
||||
"git.cloonar.com/openclawd/iso-bot/pkg/plugin"
|
||||
"git.cloonar.com/openclawd/iso-bot/plugins/d2r"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Command line flags
|
||||
game := flag.String("game", "d2r", "game plugin to load")
|
||||
routine := flag.String("routine", "mephisto", "farming routine to run")
|
||||
apiAddr := flag.String("api", ":8080", "API server address")
|
||||
configFile := flag.String("config", "", "path to config file (YAML)")
|
||||
devMode := flag.Bool("dev", false, "enable development mode (no input simulation, serves dev dashboard)")
|
||||
captureFile := flag.String("capture-file", "", "file to capture from (for dev/testing)")
|
||||
captureDir := flag.String("capture-dir", "", "directory to capture from (for dev/testing)")
|
||||
flag.Parse()
|
||||
|
||||
log.Printf("iso-bot starting: game=%s routine=%s api=%s dev=%t", *game, *routine, *apiAddr, *devMode)
|
||||
|
||||
if *configFile != "" {
|
||||
log.Printf("Loading config from %s", *configFile)
|
||||
// TODO: Load YAML config
|
||||
}
|
||||
|
||||
// Set up capture source
|
||||
var captureSource string
|
||||
var captureConfig map[string]interface{}
|
||||
|
||||
if *devMode {
|
||||
if *captureFile != "" {
|
||||
captureSource = *captureFile
|
||||
captureConfig = map[string]interface{}{
|
||||
"path": *captureFile,
|
||||
"type": "image",
|
||||
"loop": true,
|
||||
}
|
||||
} else if *captureDir != "" {
|
||||
captureSource = *captureDir
|
||||
captureConfig = map[string]interface{}{
|
||||
"path": *captureDir,
|
||||
"type": "directory",
|
||||
"frame_rate": 1.0, // 1 FPS for directory scanning
|
||||
"loop": true,
|
||||
}
|
||||
} else {
|
||||
// Default test image
|
||||
testDataPath := filepath.Join("testdata", "d2r_1080p.png")
|
||||
captureSource = testDataPath
|
||||
captureConfig = map[string]interface{}{
|
||||
"path": testDataPath,
|
||||
"type": "image",
|
||||
"loop": true,
|
||||
}
|
||||
log.Printf("No capture source specified, using default: %s", testDataPath)
|
||||
}
|
||||
} else {
|
||||
// Production mode - would use window/monitor capture
|
||||
log.Fatal("Production mode not implemented yet. Use --dev for development.")
|
||||
}
|
||||
|
||||
// Create capture backend
|
||||
registry := backends.GetDefault()
|
||||
source, err := registry.Create(backends.BackendFile, captureConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create capture source %s: %v", captureSource, err)
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
// Load game plugin
|
||||
var gamePlugin plugin.Plugin
|
||||
switch *game {
|
||||
case "d2r":
|
||||
gamePlugin = d2r.New()
|
||||
default:
|
||||
log.Fatalf("Unknown game plugin: %s", *game)
|
||||
}
|
||||
|
||||
// Create engine
|
||||
eng, err := engine.NewEngine(source, gamePlugin, *devMode)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create engine: %v", err)
|
||||
}
|
||||
|
||||
// Set up web root for dev dashboard
|
||||
webRoot := "web/dev"
|
||||
if _, err := os.Stat(webRoot); os.IsNotExist(err) {
|
||||
// Try relative to executable
|
||||
execDir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
webRoot = filepath.Join(execDir, "web", "dev")
|
||||
}
|
||||
|
||||
// Start API server
|
||||
server := api.NewServer(*apiAddr, eng, webRoot)
|
||||
go func() {
|
||||
log.Printf("Starting API server on %s", *apiAddr)
|
||||
if *devMode {
|
||||
log.Printf("Dev dashboard will be available at http://localhost%s", *apiAddr)
|
||||
}
|
||||
if err := server.Start(); err != nil {
|
||||
log.Fatalf("API server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start engine
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
if err := eng.Start(ctx); err != nil {
|
||||
log.Printf("Engine error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Printf("Game: %s, Routine: %s, Dev Mode: %t\n", *game, *routine, *devMode)
|
||||
log.Printf("Capture source: %s", captureSource)
|
||||
|
||||
// Wait for interrupt
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sig
|
||||
|
||||
log.Println("Shutting down...")
|
||||
cancel()
|
||||
eng.Stop()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue