444 lines
9.0 KiB
Go
444 lines
9.0 KiB
Go
package alchemy
|
|
|
|
import (
|
|
"autobrew/action"
|
|
"autobrew/container"
|
|
"autobrew/ingredient"
|
|
"autobrew/recipe"
|
|
"autobrew/utils"
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/MaxHalford/halfgone"
|
|
"github.com/bendahl/uinput"
|
|
"github.com/otiai10/gosseract/v2"
|
|
"github.com/vova616/screenshot"
|
|
"github.com/yassinebenaid/godump"
|
|
)
|
|
|
|
type Alchemy struct {
|
|
keyboard uinput.Keyboard
|
|
mouse uinput.Mouse
|
|
searchRect image.Rectangle
|
|
save bool
|
|
}
|
|
|
|
func NewAlchemy(mouse uinput.Mouse, keyboard uinput.Keyboard, searchRect image.Rectangle, save bool) *Alchemy {
|
|
return &Alchemy{
|
|
mouse: mouse,
|
|
keyboard: keyboard,
|
|
searchRect: searchRect,
|
|
save: save,
|
|
}
|
|
}
|
|
|
|
func (a Alchemy) findString(word string) (out gosseract.BoundingBox, err error) {
|
|
img, err := screenshot.CaptureRect(a.searchRect)
|
|
utils.Check(err)
|
|
|
|
gray := halfgone.ImageToGray(img)
|
|
gray = halfgone.ThresholdDitherer{Threshold: 85}.Apply(gray)
|
|
|
|
buf := new(bytes.Buffer)
|
|
err = png.Encode(buf, gray)
|
|
utils.Check(err)
|
|
|
|
client := gosseract.NewClient()
|
|
defer client.Close()
|
|
|
|
client.SetImageFromBytes(buf.Bytes()) // use from screen frame
|
|
|
|
if a.save {
|
|
path1 := filepath.Join(".", "output.png")
|
|
err = os.WriteFile(path1, buf.Bytes(), 0644)
|
|
utils.Check(err)
|
|
}
|
|
|
|
bounding_boxes, err := client.GetBoundingBoxes(2)
|
|
utils.Check(err)
|
|
|
|
index := slices.IndexFunc(bounding_boxes, func(e gosseract.BoundingBox) bool {
|
|
a := strings.ToLower(e.Word)
|
|
b := strings.ToLower(word)
|
|
return strings.Contains(a, b)
|
|
})
|
|
|
|
if index < 0 {
|
|
text, err := client.Text()
|
|
utils.Check(err)
|
|
// godump.Dump(bounding_boxes)
|
|
godump.Dump(text)
|
|
return out, fmt.Errorf("%s not found\n", word)
|
|
}
|
|
out = bounding_boxes[index]
|
|
|
|
return
|
|
}
|
|
|
|
func (a Alchemy) sleep(msec time.Duration) {
|
|
time.Sleep(time.Duration(msec * time.Millisecond))
|
|
}
|
|
|
|
func (a Alchemy) use() {
|
|
fmt.Println("Pressing E")
|
|
a.keyboard.KeyPress(uinput.KeyE)
|
|
a.sleep(200)
|
|
}
|
|
|
|
func (a Alchemy) hold() {
|
|
fmt.Println("Holding E")
|
|
a.keyboard.KeyDown(18)
|
|
a.sleep(1000)
|
|
a.keyboard.KeyUp(18)
|
|
a.sleep(200)
|
|
}
|
|
|
|
func (a Alchemy) lookAt(str string) {
|
|
a.mouse.Move(-1200, -550)
|
|
a.sleep(200)
|
|
// fmt.Printf("Looking at %s\n", str)
|
|
switch strings.ToLower(str) {
|
|
case "water":
|
|
a.mouse.Move(0, 150)
|
|
case "wine":
|
|
a.mouse.Move(100, 150)
|
|
case "oil":
|
|
a.mouse.Move(200, 150)
|
|
case "spirits":
|
|
a.mouse.Move(300, 150)
|
|
case "phial":
|
|
a.mouse.Move(105, 320)
|
|
a.sleep(2500) // make sure we're actually looking at it
|
|
case "still":
|
|
a.mouse.Move(200, 460)
|
|
case "dish":
|
|
a.mouse.Move(600, 600)
|
|
case "mortar":
|
|
a.mouse.Move(850, 500)
|
|
case "cauldron":
|
|
a.mouse.Move(530, 300)
|
|
case "topshelf_0":
|
|
a.mouse.Move(1000, 150)
|
|
case "topshelf_1":
|
|
a.mouse.Move(900, 150)
|
|
case "topshelf_2":
|
|
a.mouse.Move(760, 150)
|
|
case "bottomshelf_0":
|
|
a.mouse.Move(980, 350)
|
|
case "bottomshelf_1":
|
|
a.mouse.Move(900, 350)
|
|
case "bottomshelf_2":
|
|
a.mouse.Move(780, 350)
|
|
}
|
|
a.sleep(100)
|
|
}
|
|
|
|
func (a Alchemy) OpenInventory() {
|
|
fmt.Println("Opening Inventory")
|
|
a.keyboard.KeyPress(uinput.KeyI)
|
|
a.sleep(400)
|
|
}
|
|
|
|
func (a Alchemy) SelectIngredient(str ingredient.Ingredient) {
|
|
rect, err := a.findString(str.Name())
|
|
utils.Check(err)
|
|
|
|
x := rect.Box.Min.X + (rect.Box.Bounds().Size().X / 2)
|
|
y := rect.Box.Min.Y + (rect.Box.Bounds().Size().Y / 2)
|
|
|
|
a.mouse.Move(-2560, -1440)
|
|
a.sleep(100)
|
|
|
|
fmt.Printf("Selecting %s at %dx%d\n", str.Name(), x, y)
|
|
|
|
a.mouse.Move(int32(x), int32(y))
|
|
a.sleep(50)
|
|
a.mouse.LeftClick()
|
|
a.mouse.LeftClick()
|
|
a.sleep(50)
|
|
}
|
|
|
|
func (a Alchemy) CloseInventory() {
|
|
fmt.Println("Closing Inventory")
|
|
a.keyboard.KeyPress(uinput.KeyEsc)
|
|
a.sleep(300)
|
|
}
|
|
|
|
func (a Alchemy) ClosePopup() {
|
|
fmt.Println("Closing Popup")
|
|
a.keyboard.KeyPress(uinput.KeyEsc)
|
|
a.sleep(300)
|
|
}
|
|
|
|
func (a Alchemy) PourBase(i ingredient.Ingredient) {
|
|
a.lookAt(i.Name())
|
|
a.sleep(100)
|
|
a.use()
|
|
a.sleep(7980)
|
|
}
|
|
|
|
func (a Alchemy) GrabIngredient(i ingredient.Ingredient, shelf []ingredient.Ingredient) {
|
|
|
|
// fmt.Printf("Looking for %s\n", i.Name())
|
|
|
|
bottomShelf := utils.Filter(shelf, func(i ingredient.Ingredient) bool {
|
|
return i.BottomShelf()
|
|
})
|
|
|
|
topShelf := utils.Filter(shelf, func(i ingredient.Ingredient) bool {
|
|
return !i.BottomShelf()
|
|
})
|
|
|
|
topIndex := slices.Index(topShelf[:], i)
|
|
|
|
if topIndex > -1 {
|
|
// fmt.Printf("Found %s on the top shelf at index %d\n", i.Name(), topIndex)
|
|
a.sleep(1000)
|
|
switch topIndex {
|
|
case 0:
|
|
a.lookAt("topshelf_0")
|
|
case 1:
|
|
a.lookAt("topshelf_1")
|
|
case 2:
|
|
a.lookAt("topshelf_2")
|
|
}
|
|
} else {
|
|
bottomIndex := slices.Index(bottomShelf[:], i)
|
|
if bottomIndex == -1 {
|
|
return
|
|
}
|
|
|
|
// fmt.Printf("Found %s on the bottom shelf at index %d\n", i.Name(), bottomIndex)
|
|
switch bottomIndex {
|
|
case 0:
|
|
a.lookAt("bottomshelf_0")
|
|
case 1:
|
|
a.lookAt("bottomshelf_1")
|
|
case 2:
|
|
a.lookAt("bottomshelf_2")
|
|
}
|
|
}
|
|
|
|
a.sleep(500)
|
|
a.use()
|
|
a.sleep(5000)
|
|
}
|
|
|
|
func (a Alchemy) DropIn(c container.Container) {
|
|
switch c {
|
|
case container.Cauldron:
|
|
a.lookAt("cauldron")
|
|
case container.Dish:
|
|
a.lookAt("dish")
|
|
case container.Mortar:
|
|
a.lookAt("mortar")
|
|
default:
|
|
panic(fmt.Sprintf("unexpected container.Container: %#v", c))
|
|
}
|
|
a.sleep(100)
|
|
a.use()
|
|
a.sleep(5500)
|
|
}
|
|
|
|
func (a Alchemy) Grind() {
|
|
a.lookAt("mortar")
|
|
a.use()
|
|
a.sleep(10000)
|
|
}
|
|
|
|
func (a Alchemy) Boil(turns int) {
|
|
a.lookAt("cauldron")
|
|
|
|
a.sleep(100)
|
|
a.keyboard.KeyPress(uinput.KeyX)
|
|
a.sleep(5000)
|
|
|
|
for range turns {
|
|
// fmt.Printf("Boiling for %d turns\n", turns-i)
|
|
a.sleep(9000)
|
|
}
|
|
a.keyboard.KeyPress(uinput.KeyX)
|
|
a.sleep(5000)
|
|
}
|
|
|
|
func (a Alchemy) BoilBellows(turns int) {
|
|
// var duration time.Duration = 10
|
|
a.lookAt("cauldron")
|
|
|
|
a.sleep(100)
|
|
a.keyboard.KeyPress(uinput.KeyX)
|
|
a.sleep(5000) // 4+1 seconds to lower the cauldron and one for pre-boil
|
|
|
|
for range turns {
|
|
// fmt.Printf("Bellow Boiling for %d turns\n", turns-i)
|
|
|
|
for range 18 * 2 {
|
|
a.keyboard.KeyPress(uinput.KeyQ) // tap q every 500ms
|
|
a.sleep(250) // boil time, 18*500 makes 9000 again
|
|
}
|
|
}
|
|
|
|
a.sleep(2000)
|
|
|
|
a.keyboard.KeyPress(uinput.KeyX)
|
|
a.sleep(5000) // 5 seconds to pull it back up and stop animating
|
|
}
|
|
|
|
func (a Alchemy) PreparePhial() {
|
|
a.lookAt("phial")
|
|
a.use()
|
|
a.sleep(2000)
|
|
}
|
|
|
|
func (a Alchemy) Distill() {
|
|
a.lookAt("still")
|
|
a.hold()
|
|
a.sleep(16000)
|
|
}
|
|
|
|
func (a Alchemy) PourOut() {
|
|
a.lookAt("cauldron")
|
|
a.hold()
|
|
a.sleep(8000)
|
|
}
|
|
|
|
func (a Alchemy) GrindOut() {
|
|
a.lookAt("mortar")
|
|
a.hold()
|
|
a.sleep(15000)
|
|
}
|
|
|
|
func (a Alchemy) innerBrew(r recipe.Recipe) {
|
|
for _, step := range r.Steps {
|
|
switch step.Action {
|
|
case action.Prepare:
|
|
ing := utils.DedupeSlice(step.Ingredients)
|
|
base := utils.Filter(ing, func(i ingredient.Ingredient) bool {
|
|
return i.IngredientType == ingredient.Base
|
|
})
|
|
if len(base) == 0 {
|
|
panic("Recipe is missing a base")
|
|
}
|
|
|
|
a.OpenInventory()
|
|
for _, i := range ing {
|
|
if i.IngredientType != ingredient.Base {
|
|
// r.Ingredients = append(r.Ingredients, i)
|
|
a.SelectIngredient(i)
|
|
}
|
|
}
|
|
a.CloseInventory()
|
|
|
|
a.PourBase(base[0])
|
|
|
|
case action.AddToCauldron:
|
|
for _, i := range step.Ingredients {
|
|
a.GrabIngredient(i, r.Ingredients)
|
|
a.DropIn(container.Cauldron)
|
|
}
|
|
case action.Boil:
|
|
for _, i := range step.Ingredients {
|
|
a.GrabIngredient(i, r.Ingredients)
|
|
a.DropIn(container.Cauldron)
|
|
}
|
|
|
|
a.Boil(step.Turns)
|
|
|
|
case action.BoilBellows:
|
|
for _, i := range step.Ingredients {
|
|
a.GrabIngredient(i, r.Ingredients)
|
|
a.DropIn(container.Cauldron)
|
|
}
|
|
|
|
a.BoilBellows(step.Turns)
|
|
|
|
case action.GrindIngredients:
|
|
for _, i := range step.Ingredients {
|
|
a.GrabIngredient(i, r.Ingredients)
|
|
a.DropIn(container.Mortar)
|
|
}
|
|
|
|
a.Grind()
|
|
a.DropIn(container.Cauldron)
|
|
|
|
// finishers
|
|
case action.PourPhial:
|
|
a.PreparePhial()
|
|
a.PourOut()
|
|
|
|
case action.Distill:
|
|
a.PreparePhial()
|
|
a.Distill()
|
|
|
|
case action.GrindPotion:
|
|
a.GrindOut()
|
|
|
|
default:
|
|
panic(fmt.Sprintf("unexpected main.StepAction: %#v", step.Action))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a Alchemy) Brew(book map[string][]recipe.Step, name string, amount int) {
|
|
r := recipe.Recipe{
|
|
Steps: book[name],
|
|
}
|
|
r.LoadIngredients()
|
|
|
|
ingredients := make(map[string]int)
|
|
|
|
for _, i := range r.Ingredients {
|
|
ingredients[i.Name()]++
|
|
}
|
|
|
|
formatted := make([]string, 0)
|
|
|
|
for k, v := range ingredients {
|
|
formatted = append(formatted, fmt.Sprintf("%dx %s", v, k))
|
|
}
|
|
|
|
fmt.Printf("%s\n\nIngredients:\n\t%s\n\n", name, strings.Join(formatted, "\n\t"))
|
|
|
|
a.sleep(1000)
|
|
|
|
totalStartTime := time.Now()
|
|
if amount == -1 {
|
|
i := 0
|
|
for {
|
|
fmt.Printf("Start brewing %s in 3\n", name)
|
|
a.sleep(1000)
|
|
fmt.Printf("Start brewing %s in 2\n", name)
|
|
a.sleep(1000)
|
|
fmt.Printf("Start brewing %s in 1\n", name)
|
|
a.sleep(1000)
|
|
|
|
startTime := time.Now()
|
|
a.innerBrew(r)
|
|
fmt.Printf("Batch %d took %s, total %s\n", i+1, time.Since(startTime), time.Since(totalStartTime))
|
|
a.ClosePopup()
|
|
i++
|
|
}
|
|
}
|
|
|
|
for i := range amount {
|
|
fmt.Printf("Start brewing %s in 3\n", name)
|
|
a.sleep(1000)
|
|
fmt.Printf("Start brewing %s in 2\n", name)
|
|
a.sleep(1000)
|
|
fmt.Printf("Start brewing %s in 1\n", name)
|
|
a.sleep(1000)
|
|
|
|
startTime := time.Now()
|
|
a.innerBrew(r)
|
|
fmt.Printf("Batch %d took %s, total %s\n", i+1, time.Since(startTime), time.Since(totalStartTime))
|
|
a.ClosePopup()
|
|
}
|
|
}
|