The Wiki for Tale 8 is in read-only mode and is available for archival and reference purposes only. Please visit the current Tale 11 Wiki in the meantime.
If you have any issues with this Wiki, please post in #wiki-editing on Discord or contact Brad in-game.
Difference between revisions of "User:Augir/Necklace"
Line 22: | Line 22: | ||
Small Coral good color | Small Coral good color | ||
+ | |||
Legend: | Legend: | ||
Line 29: | Line 30: | ||
:X = Ruled out for that slot | :X = Ruled out for that slot | ||
:C = Correct Color, Correct Slot | :C = Correct Color, Correct Slot | ||
+ | |||
+ | -- Vegetable Macro for Tale 7 by thejanitor. | ||
+ | -- | ||
+ | -- Thanks to veggies.lua for the build button locations | ||
+ | -- Updated 29-SEP-2017 by Silden to take into account UI changes that meant the windows would not close properly | ||
+ | -- Updated 30-SEP-2017 by Silden to increase default values to cater for long veg names, such as Cabbage | ||
+ | |||
+ | dofile("common.inc") | ||
+ | dofile("settings.inc") | ||
+ | |||
+ | |||
+ | WARNING=[[ | ||
+ | THIS IS A BETA MACRO YOU ARE USING AT YOUR OWN RISK | ||
+ | You must be in the fully zoomed in top down F8 F8 F8 view, Alt+L to lock the camere once there. | ||
+ | In User Options -> Interface Options -> Menu You must DISABLE: "Right-Click Pins/Unpins a Menu" | ||
+ | You Must ENABLE: "Right-Click opens a Menu as Pinned" | ||
+ | You Must ENABLE: "Use the chat area instead of popups for many messages" | ||
+ | In Options -> One-Click and Related -> You must DISABLE: "Plant all crops where you stand" | ||
+ | In Options -> Video -> You must set: Shadow Quality and Time of Day lighting to the lowest possible. | ||
+ | Do not move once the macro is running and you must be standing on a tile with water available to refill. | ||
+ | Do not stand directly on or within planting distance of actual animated water. | ||
+ | ]] | ||
+ | |||
+ | DEBUG=false | ||
+ | |||
+ | -- How many times to search for a plant / try open a bed window before giving up. | ||
+ | SEARCH_RETRYS=10 | ||
+ | |||
+ | -- How many pixels in a NxN grid to look for before | ||
+ | MATCH_GRID_SIZE=5 | ||
+ | |||
+ | -- These are the times in seconds it waits before watering a plant for a given stage. | ||
+ | -- For example, plant A is planted at time 0. At time 2.8 seconds the macro queues up plant A to be watered, it then | ||
+ | -- sleeps 0.2 seconds until FIRST_STAGE_WAIT time has passed before watering that plant an moving on. | ||
+ | |||
+ | -- If you encounter problems where plants are dieing at various stages it is ethier because these values are too low | ||
+ | -- causing a plant to be watered 3+ times in a single stage before it grows. Or it is because they are too high and | ||
+ | -- a plant is not recieving its water in time before regressing a stage. | ||
+ | -- Finally if in the final harvest stage you get getting messages about running out of water then probably it is trying | ||
+ | -- to harvest before it is ready and trying to rewater a plants 3rd stage. Increase the harvest wait to hopefully fix this. | ||
+ | |||
+ | -- TODO: Scale these based on global (and ideally local) teppy time. | ||
+ | FIRST_STAGE_WAIT = 4 | ||
+ | SECOND_STAGE_WAIT = 30 | ||
+ | THIRD_STAGE_WAIT = 48 | ||
+ | FORTH_STAGE_WAIT = 70 | ||
+ | HARVEST_STAGE_WAIT = 83 | ||
+ | |||
+ | STAGE_WAITS = { FIRST_STAGE_WAIT, SECOND_STAGE_WAIT, THIRD_STAGE_WAIT, FORTH_STAGE_WAIT, HARVEST_STAGE_WAIT } | ||
+ | |||
+ | -- How long to wait for the characters animations to stop at the end of each planting run. If this is too low | ||
+ | -- then instead of clicking a newly placed plant the macro will hit your character. So if you see at the start of a new | ||
+ | -- cycle the character menu being opened by the macro increase this value. | ||
+ | END_OF_RUN_WAIT = 3000 | ||
+ | |||
+ | -- Controls the size of each search box. The larger this is the slower the search phase which can break everything. | ||
+ | SEARCH_BOX_SCALE = 1/10 | ||
+ | |||
+ | MAX_PLANTS=12 | ||
+ | |||
+ | RED = 0xFF2020ff | ||
+ | BLACK = 0x000000ff | ||
+ | WHITE = 0xFFFFFFff | ||
+ | |||
+ | LEEKS = "Horus' Grain" | ||
+ | SEED_NAMES = { | ||
+ | "Tears of Sinai", | ||
+ | "Green Leaf", | ||
+ | "Bastet's Yielding", | ||
+ | LEEKS, | ||
+ | "Apep's Crop", | ||
+ | } | ||
+ | SEED_TYPES = { | ||
+ | "Onions", | ||
+ | "Carrots", | ||
+ | "Cabbage", | ||
+ | "Leeks", | ||
+ | "Garlic", | ||
+ | } | ||
+ | |||
+ | -- Used to control the plant window placement and tiling. | ||
+ | WINDOW_HEIGHT=120 -- Was 80 | ||
+ | WINDOW_WIDTH=285 -- Was 220 | ||
+ | WINDOW_OFFSET_X=150 | ||
+ | WINDOW_OFFSET_Y=150 | ||
+ | |||
+ | function doit() | ||
+ | while true do | ||
+ | local config = makeReadOnly(getUserParams()) | ||
+ | askForWindowAndSetupGlobals(config) | ||
+ | gatherVeggies(config) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | function askForWindowAndSetupGlobals(config) | ||
+ | local min_jugs = config.num_waterings * config.num_plants * config.num_stages | ||
+ | local min_seeds = config.num_plants + 8 | ||
+ | local one = 'You will need ' .. min_jugs .. ' jugs of water and at minimum ' .. min_seeds .. ' seeds \n' | ||
+ | local two = '\n Press Shift over ATITD window to continue.' | ||
+ | askForWindow(one .. two) | ||
+ | setupGlobals() | ||
+ | end | ||
+ | |||
+ | function setupGlobals() | ||
+ | NORTH = Vector:new{0,-1} | ||
+ | SOUTH = Vector:new{0,1} | ||
+ | WEST = Vector:new{-1,0} | ||
+ | EAST = Vector:new{1,0} | ||
+ | NORTH_WEST = NORTH + WEST | ||
+ | NORTH_EAST = NORTH + EAST | ||
+ | SOUTH_WEST = SOUTH + WEST | ||
+ | SOUTH_EAST = SOUTH + EAST | ||
+ | DOUBLE_SOUTH = SOUTH * 2 | ||
+ | DOUBLE_NORTH = NORTH * 2 | ||
+ | DOUBLE_WEST = WEST * 2 | ||
+ | DOUBLE_EAST = EAST * 2 | ||
+ | |||
+ | MOVE_BTNS = {} | ||
+ | PLANT_LOCATIONS = {next=1} | ||
+ | PlantLocation:new{direction_vector=NORTH, move_btn=Vector:new{59, 51}} | ||
+ | PlantLocation:new{direction_vector=EAST ,move_btn=Vector:new{84, 74}} | ||
+ | PlantLocation:new{direction_vector=SOUTH ,move_btn=Vector:new{60, 98}} | ||
+ | PlantLocation:new{direction_vector=WEST ,move_btn=Vector:new{37, 75}} | ||
+ | PlantLocation:new{direction_vector=NORTH, num_move_steps=2} | ||
+ | PlantLocation:new{direction_vector=WEST, num_move_steps=2} | ||
+ | PlantLocation:new{direction_vector=EAST, num_move_steps=2} | ||
+ | PlantLocation:new{direction_vector=SOUTH, num_move_steps=2} | ||
+ | PlantLocation:new{direction_vector=NORTH_EAST, move_btn=Vector:new{75, 62}} | ||
+ | PlantLocation:new{direction_vector=NORTH_WEST, move_btn=Vector:new{45,60}} | ||
+ | PlantLocation:new{direction_vector=SOUTH_EAST, move_btn=Vector:new{75, 91}} | ||
+ | PlantLocation:new{direction_vector=SOUTH_WEST, move_btn=Vector:new{45, 87}} | ||
+ | makeReadOnly(PLANT_LOCATIONS) | ||
+ | |||
+ | local mid = getScreenMiddle() | ||
+ | ANIMATION_BOX = makeBox(mid.x - 60, mid.y - 50, 105, 85) | ||
+ | ARM_BOX = makeBox(mid.x - 90, mid.y - 20, 80, 25) | ||
+ | BUILD_BTN = Vector:new{31, 135} | ||
+ | end | ||
+ | |||
+ | PlantLocation={} | ||
+ | function PlantLocation:new(o) | ||
+ | if o.num_move_steps then | ||
+ | o.move_btn = PLANT_LOCATIONS[o.direction_vector].move_btn | ||
+ | o.direction_vector = o.direction_vector * o.num_move_steps | ||
+ | else | ||
+ | o.num_move_steps = 1 | ||
+ | end | ||
+ | PLANT_LOCATIONS[o.direction_vector] = o | ||
+ | PLANT_LOCATIONS[PLANT_LOCATIONS.next] = o | ||
+ | PLANT_LOCATIONS.next = PLANT_LOCATIONS.next + 1 | ||
+ | o.box = makeSearchBox(o.direction_vector) | ||
+ | return newObject(PlantLocation, o, true) | ||
+ | end | ||
+ | |||
+ | function PlantLocation:move() | ||
+ | for step=1,self.num_move_steps do | ||
+ | click(self.move_btn) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | function gatherVeggies(config) | ||
+ | local plants = Plants:new{num_plants=config.num_plants } | ||
+ | |||
+ | drawWater() | ||
+ | for _=1,config.num_runs do | ||
+ | local start = lsGetTimer() | ||
+ | |||
+ | checkBreak() | ||
+ | --lsSleep(3000) | ||
+ | |||
+ | plants:iterate(Plant.plant, config) | ||
+ | for round=1,config.num_stages+1 do | ||
+ | plants:iterate(Plant.water, {stage_wait=STAGE_WAITS[round], num_waterings=config.num_waterings}) | ||
+ | checkBreak() | ||
+ | end | ||
+ | |||
+ | plants:iterate(Plant.close, config) | ||
+ | |||
+ | lsSleep(click_delay) | ||
+ | drawWater() | ||
+ | lsSleep(click_delay*5) | ||
+ | checkBreak() | ||
+ | |||
+ | |||
+ | local stop = lsGetTimer() + END_OF_RUN_WAIT | ||
+ | local total = math.floor((3600 / ((stop - start)/1000)) * config.num_plants * 9) -- default 3, currently 9 veggie yield with pyramids bonus | ||
+ | sleepWithStatus(END_OF_RUN_WAIT, "Running at " .. total .. " veg per hour. ") | ||
+ | end | ||
+ | end | ||
+ | |||
+ | -- Simple container object which constructs N plants and allows iteration over them. | ||
+ | Plants={} | ||
+ | function Plants:new(o) | ||
+ | for index=1,o.num_plants do | ||
+ | local location = PLANT_LOCATIONS[index] | ||
+ | self[index] = Plant:new{index=index, location=location} | ||
+ | end | ||
+ | return newObject(self,o,true) | ||
+ | end | ||
+ | |||
+ | function Plants:iterate(func, args) | ||
+ | for index=1,self.num_plants do | ||
+ | func(self[index], args) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | Plant = {} | ||
+ | function Plant:new(o) | ||
+ | o.window_pos = indexToWindowPos(o.index) | ||
+ | return newObject(self,o) | ||
+ | end | ||
+ | |||
+ | function Plant:plant(config) | ||
+ | -- Take of a snapshot of the area in which we are guessing the plant will be placed before we actually create | ||
+ | -- and place it. | ||
+ | if not self.saved_plant_location then | ||
+ | lsSleep(click_delay) | ||
+ | self.beforePlantPixels = getBoxPixels(self.location.box) | ||
+ | end | ||
+ | |||
+ | clickPlantButton(config.seed_name) | ||
+ | self.location:move() | ||
+ | local spot = getWaitSpotAt(BUILD_BTN) | ||
+ | click(BUILD_BTN) | ||
+ | self.plant_time = lsGetTimer() | ||
+ | waitForChange(spot, click_delay*5) | ||
+ | lsSleep(click_delay) | ||
+ | |||
+ | if not self.saved_plant_location then | ||
+ | for _=1,SEARCH_RETRYS do | ||
+ | if self:searchForPlant() then | ||
+ | break | ||
+ | end | ||
+ | lsPrintln("Search retry for plant " .. self.index) | ||
+ | lsSleep(tick_delay) | ||
+ | end | ||
+ | if not self.saved_plant_location then | ||
+ | lsPrintln("Fail search for plant" .. self.index) | ||
+ | end | ||
+ | |||
+ | end | ||
+ | |||
+ | self:openBedWindow(config.alternate_drag) | ||
+ | end | ||
+ | |||
+ | function Plant:searchForPlant() | ||
+ | lsPrintln("Searching for plant " .. self.index) | ||
+ | return findChangedRow(self.location.box, self.beforePlantPixels, | ||
+ | function (location, pixels) | ||
+ | self.saved_plant_location = location | ||
+ | self.saved_plant_pixels = pixels | ||
+ | end | ||
+ | ) | ||
+ | end | ||
+ | |||
+ | function Plant:openBedWindow(alternate_drag) | ||
+ | if not self.saved_plant_location then | ||
+ | lsPrintln("No Saved location for plant " .. self.index) | ||
+ | return | ||
+ | end | ||
+ | |||
+ | -- Wierd hacky thing, move the mouse to where the window will be and then safeClick the plant which causes | ||
+ | -- the window to open instantly at the desired location and not where we clicked the plant. | ||
+ | -- TODO: problably do something different as this is the only thing that takes mouse control from the user. | ||
+ | |||
+ | self.window_open = false | ||
+ | local window_spot = getWaitSpotAt(self.window_pos + {5,5}) | ||
+ | for _=1, SEARCH_RETRYS do | ||
+ | if waitForPixelsAt(self.saved_plant_location, self.location.box, self.saved_plant_pixels) then | ||
+ | if alternate_drag then | ||
+ | local open_spot = getWaitSpotAt(self.saved_plant_location + {5,5}) | ||
+ | click(self.saved_plant_location,true,true) | ||
+ | if waitForChange(open_spot,click_delay*5) then | ||
+ | lsSleep(click_delay) | ||
+ | drag(self.saved_plant_location.x+5,self.saved_plant_location.y+5,self.window_pos.x,self.window_pos.y,click_delay*2) | ||
+ | end | ||
+ | else | ||
+ | moveMouse(self.window_pos) | ||
+ | click(self.saved_plant_location ,1) | ||
+ | end | ||
+ | |||
+ | self.window_open = waitForChange(window_spot, click_delay*5) | ||
+ | if self.window_open then | ||
+ | break | ||
+ | end | ||
+ | end | ||
+ | lsPrintln("Bed window open retry for plant " .. self.index) | ||
+ | lsSleep(click_delay) | ||
+ | end | ||
+ | |||
+ | if not self.window_open then | ||
+ | lsPrintln("Bed window open fail for plant " .. self.index) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | function waitForPixelsAt(vector, box, pixels) | ||
+ | local match_size = MATCH_GRID_SIZE | ||
+ | local half_match_size = math.floor(match_size/2) | ||
+ | local box_location_x = vector.x - half_match_size | ||
+ | local box_location_y = vector.y - half_match_size | ||
+ | local match_box = makeBox(box_location_x, box_location_y, match_size, match_size) | ||
+ | local all_same = true | ||
+ | iterateBoxPixels(match_box, function(x,y,pixel) | ||
+ | local pixels_x = box_location_x + x - box.left | ||
+ | local pixels_y = box_location_y + y - box.top | ||
+ | local old_pixel = pixels[pixels_y][pixels_x] | ||
+ | local diff = calculatePixelDiffs(old_pixel, pixel) | ||
+ | local diff_ok = diff[1] < 10 and diff[2] < 10 and diff[3] < 10 | ||
+ | all_same = all_same and diff_ok | ||
+ | end) | ||
+ | return all_same | ||
+ | end | ||
+ | |||
+ | |||
+ | -- For a given plants index sleep until time_seconds has passed for that plant since it was planted. | ||
+ | function Plant:sleepUntil(time_seconds) | ||
+ | local sleepTime = time_seconds*1000 - (lsGetTimer() - self.plant_time); | ||
+ | if sleepTime > 0 then | ||
+ | sleepWithStatus(sleepTime, "Sleeping for " .. sleepTime) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | function Plant:clickWindow(offset,right_click,show) | ||
+ | if self.window_open then | ||
+ | click(self.window_pos + offset,right_click,show) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | function Plant:water(args) | ||
+ | if not self.window_open then | ||
+ | lsPrintln("Trying to water plant " .. self.index .. " which has no window open") | ||
+ | return | ||
+ | end | ||
+ | |||
+ | checkBreak() | ||
+ | self:sleepUntil(args.stage_wait) | ||
+ | |||
+ | local search_box = makeBox(self.window_pos.x,self.window_pos.y-40,40,40) | ||
+ | srReadScreen() | ||
+ | local this_loc = srFindImageInRange("This.png", search_box.left, search_box.top, search_box.width, search_box.height, 4800) | ||
+ | if not this_loc then | ||
+ | lsPrintln("Didn't find This text for plant " .. self.index) | ||
+ | self.window_open = false | ||
+ | return | ||
+ | end | ||
+ | this_loc = Vector:new{x=this_loc[0],y=this_loc[1]+10} | ||
+ | |||
+ | click(this_loc) | ||
+ | for _=1, args.num_waterings do | ||
+ | lsSleep(click_delay) | ||
+ | click(this_loc+{0,25}) | ||
+ | click(this_loc) | ||
+ | checkBreak() | ||
+ | end | ||
+ | end | ||
+ | |||
+ | function Plant:close(config) | ||
+ | -- New UI changes affected the closing of finished windows. | ||
+ | -- Now look for the UnPin image, and safeClick it closed if found. | ||
+ | |||
+ | local search_box = makeBox(self.window_pos.x+65,self.window_pos.y-50,220,80) | ||
+ | srReadScreen() | ||
+ | local unpin_loc = srFindImageInRange("UnPin.png", search_box.left, search_box.top, search_box.width, search_box.height, 4800) | ||
+ | if unpin_loc then | ||
+ | safeClick(unpin_loc[0],unpin_loc[1]); | ||
+ | else | ||
+ | lsPrintln("Didn't find unpin image for plant " .. self.index .. " looking left:" .. search_box.left .. ", top: " .. search_box.top .. ", width: " .. search_box.width .. ", height: " .. search_box.height .. "."); | ||
+ | return | ||
+ | end | ||
+ | end | ||
+ | |||
+ | -- Create a table of direction string -> box. Each box is where we will search the plant placed for that given direction | ||
+ | -- string. | ||
+ | -- Full of janky hardcoded values. | ||
+ | -- TODO: Make debuging this easier, figure out pixel scaling for different resolutions, get rid of magic numbers. | ||
+ | function makeSearchBox(direction) | ||
+ | local xyWindowSize = srGetWindowSize() | ||
+ | local search_size = math.floor(xyWindowSize[0] * SEARCH_BOX_SCALE) | ||
+ | local mid = getScreenMiddle() | ||
+ | local offset_mid = mid - {math.floor(search_size / 3), math.floor(search_size / 3) } | ||
+ | |||
+ | local top_left = offset_mid + direction*40 - Vector:new{20,20 } | ||
+ | |||
+ | local box = makeBox(top_left.x,top_left.y, search_size, search_size) | ||
+ | box.direction = direction | ||
+ | return box | ||
+ | end | ||
+ | |||
+ | function getScreenMiddle() | ||
+ | local xyWindowSize = srGetWindowSize() | ||
+ | return Vector:new{math.floor(xyWindowSize[0]/2), math.floor(xyWindowSize[1]/2)} | ||
+ | end | ||
+ | |||
+ | -- Tiling method from Cinganjehoi's original bash script. Tried out the automato common ones but they are slow | ||
+ | -- and broke sometimes? This is super simple and its not the end of the world if it breaks a little during a run. | ||
+ | function indexToWindowPos(index) | ||
+ | local columns = getNumberWindowColumns() | ||
+ | local x = WINDOW_WIDTH*((index-1) % columns) + WINDOW_OFFSET_X | ||
+ | local y = WINDOW_HEIGHT*math.floor((index-1) / columns) + WINDOW_OFFSET_Y | ||
+ | return Vector:new{x, y} | ||
+ | end | ||
+ | |||
+ | function getNumberWindowColumns() | ||
+ | local xyWindowSize = srGetWindowSize() | ||
+ | local width = xyWindowSize[0] * 0.6 | ||
+ | return math.floor(width / WINDOW_WIDTH); | ||
+ | end | ||
+ | |||
+ | function clickPlantButton(seed_name) | ||
+ | local build_menu_opened = false | ||
+ | while not build_menu_opened do | ||
+ | local plantButton = findText(seed_name) | ||
+ | if plantButton then | ||
+ | local spot = getWaitSpotAt(Vector:new{5,5}) | ||
+ | clickText(plantButton, 1) | ||
+ | build_menu_opened = waitForChange(spot,click_delay*5) | ||
+ | else | ||
+ | lsPlaySound("fail.wav"); | ||
+ | error("Text " .. seed_name .. " Not found.") | ||
+ | end | ||
+ | sleepWithStatus(tick_delay, "Planting...") --Retrying build menu open | ||
+ | end | ||
+ | end | ||
+ | |||
+ | function getBoxPixels(box) | ||
+ | local pixels = {} | ||
+ | iterateBoxPixels(box, | ||
+ | function(x,y,pixel) | ||
+ | pixels[y][x] = pixel | ||
+ | end, | ||
+ | function(y) | ||
+ | pixels[y] = {} | ||
+ | end | ||
+ | ) | ||
+ | return pixels | ||
+ | end | ||
+ | |||
+ | -- Finds a row of pixels which have changed in the current ReadScreen buffer compared to a given 2d array of pixels. | ||
+ | function findChangedRow(box, pixels, func) | ||
+ | if DEBUG then | ||
+ | debugShowBox(box) | ||
+ | end | ||
+ | |||
+ | local changed = {} | ||
+ | local new_pixels = {} | ||
+ | iterateBoxPixels(box, | ||
+ | function(x,y,pixel) | ||
+ | changed[y][x] = pixels[y][x] ~= pixel | ||
+ | new_pixels[y][x] = pixel | ||
+ | end, | ||
+ | function(y) | ||
+ | changed[y] = {} | ||
+ | new_pixels[y] = {} | ||
+ | end | ||
+ | ) | ||
+ | |||
+ | local search_from_left = box.direction.x <= 0 | ||
+ | local search_from_top = box.direction.y < 0 | ||
+ | |||
+ | local y_start, y_end, y_inc = 0, math.floor(box.height/MATCH_GRID_SIZE)-1, 1 | ||
+ | if not search_from_top then | ||
+ | y_start, y_end, y_inc = y_end, y_start, -1 | ||
+ | end | ||
+ | local x_start, x_end, x_inc = 0, math.floor(box.width/MATCH_GRID_SIZE)-1, 1 | ||
+ | if not search_from_left then | ||
+ | x_start, x_end, x_inc = x_end, x_start, -1 | ||
+ | end | ||
+ | |||
+ | for y=y_start,y_end,y_inc do | ||
+ | for x=x_start,x_end,x_inc do | ||
+ | local all_changed = true | ||
+ | for j=0,MATCH_GRID_SIZE-1 do | ||
+ | for k=0,MATCH_GRID_SIZE-1 do | ||
+ | local changed_x = x*MATCH_GRID_SIZE+k | ||
+ | local changed_y = y*MATCH_GRID_SIZE+j | ||
+ | all_changed = all_changed and changed[changed_y][changed_x] | ||
+ | end | ||
+ | end | ||
+ | if all_changed then | ||
+ | local grid_centre_x = x*MATCH_GRID_SIZE + math.floor(MATCH_GRID_SIZE/2) | ||
+ | local grid_centre_y = y*MATCH_GRID_SIZE + math.floor(MATCH_GRID_SIZE/2) | ||
+ | func(Vector:new{box.left + grid_centre_x,box.top + grid_centre_y},new_pixels) | ||
+ | return true | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | return false | ||
+ | end | ||
+ | |||
+ | function debugShowBox(box) | ||
+ | srSetMousePos(box.left, box.top) | ||
+ | sleepWithStatus(2000, "TOP LEFT") | ||
+ | srSetMousePos(box.right, box.bottom) | ||
+ | sleepWithStatus(2000, "BOT RIGHT") | ||
+ | end | ||
+ | |||
+ | function inside(vector,box) | ||
+ | local x, y = vector.x, vector.y | ||
+ | return (x >= box.left and x <= box.right) and (y >= box.top and y <= box.bottom) | ||
+ | end | ||
+ | |||
+ | function distanceCentre(vector) | ||
+ | local mid = getScreenMiddle() | ||
+ | local delta = vector - mid | ||
+ | |||
+ | local dx = math.pow(delta.x,2) | ||
+ | local dy = math.pow(delta.y,2) | ||
+ | |||
+ | return math.sqrt(dx + dy) | ||
+ | end | ||
+ | |||
+ | function iterateBoxPixels(box, xy_func, y_func) | ||
+ | srReadScreen() | ||
+ | |||
+ | |||
+ | for y=0,box.height,1 do | ||
+ | if y_func then y_func(y) end | ||
+ | for x=0, box.width do | ||
+ | local pixel = srReadPixelFromBuffer(box.left + x, box.top + y) | ||
+ | if xy_func(x,y,pixel) then | ||
+ | return | ||
+ | end | ||
+ | end | ||
+ | checkBreak() | ||
+ | end | ||
+ | end | ||
+ | |||
+ | |||
+ | -- Used to place gui elements sucessively. | ||
+ | current_y = 0 | ||
+ | -- How far off the left hand side to place gui elements. | ||
+ | X_PADDING = 5 | ||
+ | |||
+ | function getUserParams() | ||
+ | local is_done = false | ||
+ | local got_user_params = false | ||
+ | local use_custom = readSetting("use_custom",false) | ||
+ | local config = {alternate_drag=readSetting("alternate_drag")} | ||
+ | config.seed_name = readSetting("seed_name","") | ||
+ | config.num_waterings = readSetting("num_waterings",1) | ||
+ | config.num_stages = readSetting("num_stages",1) | ||
+ | local seed_index = readSetting("seed_index",1) | ||
+ | local display_seed_names = {} | ||
+ | for i=1,5 do | ||
+ | display_seed_names[i] = SEED_NAMES[i] .. " (" .. SEED_TYPES[i] .. ")" | ||
+ | end | ||
+ | while not is_done do | ||
+ | current_y = 10 | ||
+ | |||
+ | if not got_user_params then | ||
+ | local max_plants = MAX_PLANTS | ||
+ | use_custom = lsCheckBox(X_PADDING, current_y, 10, WHITE, "Use custom seed?", use_custom) | ||
+ | current_y = 45 | ||
+ | if use_custom then | ||
+ | config.seed_name = drawEditBox("seed_name", "Custom seed name? ", config.seed_name, false) | ||
+ | config.num_waterings = drawNumberEditBox("num_waterings", "How many waterings per stage? ", config.num_waterings) | ||
+ | config.num_stages = drawNumberEditBox("num_stages", "How many watering stages? ", config.num_stages) | ||
+ | else | ||
+ | seed_index = lsDropdown("seed_name", X_PADDING, current_y, 10, lsScreenX - 10, seed_index, display_seed_names) | ||
+ | current_y = 85 | ||
+ | end | ||
+ | config.num_plants = drawNumberEditBox("num_plants", "How many to plant per run? Max " .. max_plants, 12) | ||
+ | config.num_runs = drawNumberEditBox("num_runs", "How many runs? ", 20) | ||
+ | config.click_delay = drawNumberEditBox("click_delay", "What should the click delay be? ", 100) | ||
+ | config.alternate_drag = lsCheckBox(X_PADDING, current_y, 10, WHITE, "Alternate (slow) dragging?", config.alternate_drag) | ||
+ | got_user_params = true | ||
+ | for k,v in pairs(config) do | ||
+ | got_user_params = got_user_params and v | ||
+ | end | ||
+ | got_user_params = got_user_params and drawBottomButton(lsScreenX - 5, "Next step") | ||
+ | else | ||
+ | drawWrappedText(WARNING, RED, X_PADDING, current_y) | ||
+ | |||
+ | is_done = drawBottomButton(lsScreenX - 5, "Start Script") | ||
+ | end | ||
+ | |||
+ | if drawBottomButton(110, "Exit Script") then | ||
+ | error "Script exited by user" | ||
+ | end | ||
+ | |||
+ | lsDoFrame() | ||
+ | lsSleep(10) | ||
+ | end | ||
+ | |||
+ | writeSetting("seed_index",seed_index) | ||
+ | writeSetting("alternate_drag",config.alternate_drag) | ||
+ | writeSetting("use_custom",use_custom) | ||
+ | writeSetting("seed_name",config.seed_name) | ||
+ | writeSetting("num_waterings",config.num_waterings) | ||
+ | writeSetting("num_stages",config.num_stages) | ||
+ | config.num_plants = limitMaxPlants(config.num_plants) | ||
+ | if not use_custom then | ||
+ | config.seed_name = SEED_NAMES[seed_index] | ||
+ | config.num_waterings = config.seed_name == LEEKS and 3 or 2 | ||
+ | config.num_stages = 3 | ||
+ | end | ||
+ | click_delay = config.click_delay | ||
+ | return config | ||
+ | end | ||
+ | |||
+ | function limitMaxPlants(user_supplied_max_num) | ||
+ | return math.min(12, user_supplied_max_num) | ||
+ | end | ||
+ | |||
+ | function drawNumberEditBox(key, text, default) | ||
+ | return drawEditBox(key, text, default, true) | ||
+ | end | ||
+ | |||
+ | function drawEditBox(key, text, default, validateNumber) | ||
+ | drawTextUsingCurrent(text, WHITE) | ||
+ | local width = validateNumber and 50 or 200 | ||
+ | local height = 30 | ||
+ | local done, result = lsEditBox(key, X_PADDING, current_y, 0, width, height, 1.0, 1.0, BLACK, default) | ||
+ | if validateNumber then | ||
+ | result = tonumber(result) | ||
+ | elseif result == "" then | ||
+ | result = false | ||
+ | end | ||
+ | if not result then | ||
+ | local error = validateNumber and "Please enter a valid number!" or "Enter text!" | ||
+ | drawText(error, RED, X_PADDING + width + 5, current_y + 5) | ||
+ | result = false | ||
+ | end | ||
+ | current_y = current_y + 35 | ||
+ | return result | ||
+ | end | ||
+ | |||
+ | function drawTextUsingCurrent(text, colour) | ||
+ | drawText(text, colour, X_PADDING, current_y) | ||
+ | current_y = current_y + 20 | ||
+ | end | ||
+ | function drawText(text, colour, x, y) | ||
+ | lsPrint(x, y, 10, 0.7, 0.7, colour, text) | ||
+ | end | ||
+ | |||
+ | function drawWrappedText(text, colour, x, y) | ||
+ | lsPrintWrapped(x, y, 10, lsScreenX-10, 0.6, 0.6, colour, text) | ||
+ | end | ||
+ | |||
+ | function drawBottomButton(xOffset, text) | ||
+ | return lsButtonText(lsScreenX - xOffset, lsScreenY - 30, z, 100, WHITE, text) | ||
+ | end | ||
+ | |||
+ | -- Simple immutable vector class | ||
+ | Vector={} | ||
+ | function Vector:new(o) | ||
+ | o.x = o.x or o[1] | ||
+ | o.y = o.y or o[2] | ||
+ | return newObject(self, o, true) | ||
+ | end | ||
+ | |||
+ | function Vector:__add(vector) | ||
+ | local x,y = Vector.getXY(vector) | ||
+ | return Vector:new{self.x + x, self.y + y} | ||
+ | end | ||
+ | |||
+ | function Vector:__sub(vector) | ||
+ | local x,y = Vector.getXY(vector) | ||
+ | return Vector:new{self.x - x, self.y - y} | ||
+ | end | ||
+ | |||
+ | function Vector:__div(divisor) | ||
+ | return Vector:new{self.x / divisor, self.y / divisor} | ||
+ | end | ||
+ | |||
+ | function Vector:__mul(multiplicand) | ||
+ | return Vector:new{self.x * multiplicand, self.y * multiplicand} | ||
+ | end | ||
+ | |||
+ | function Vector.getXY(vector) | ||
+ | return vector.x or vector[1], vector.y or vector[2] | ||
+ | end | ||
+ | |||
+ | function Vector:length() | ||
+ | return math.sqrt(self.x^2 + self.y^2) | ||
+ | end | ||
+ | |||
+ | function Vector:normalize() | ||
+ | return self / self:length() | ||
+ | end | ||
+ | |||
+ | function Vector:__tostring() | ||
+ | return "(" .. self.x .. ", " .. self.y .. ")" | ||
+ | end | ||
+ | |||
+ | |||
+ | |||
+ | function click(vector, right_click, show_mouse) | ||
+ | if show_mouse then | ||
+ | srClickMouse(vector.x, vector.y, right_click) | ||
+ | else | ||
+ | safeClick(vector.x, vector.y, right_click) | ||
+ | end | ||
+ | lsSleep(click_delay) | ||
+ | end | ||
+ | |||
+ | function moveMouse(vector) | ||
+ | srSetMousePos(vector.x, vector.y) | ||
+ | lsSleep(click_delay) | ||
+ | end | ||
+ | |||
+ | function getWaitSpotAt(vector) | ||
+ | return getWaitSpot(vector.x, vector.y) | ||
+ | end | ||
+ | |||
+ | -- Helper function used in an objects constructor to setup its metatable correctly allowing for basic inheritence. | ||
+ | function newObject(class, o, read_only) | ||
+ | o = o or {} | ||
+ | setmetatable(o, class) | ||
+ | class.__index = class | ||
+ | if read_only then | ||
+ | makeReadOnly(o) | ||
+ | end | ||
+ | return o | ||
+ | end | ||
+ | |||
+ | function makeReadOnly(table) | ||
+ | local mt = getmetatable(table) | ||
+ | if not mt then | ||
+ | mt = {} | ||
+ | if not table then print(debug.traceback()) end | ||
+ | setmetatable(table,mt) | ||
+ | end | ||
+ | mt.__newindex = function(t,k,v) | ||
+ | error("Attempt to update a read-only table", 2) | ||
+ | end | ||
+ | return table | ||
+ | end |
Revision as of 03:52, 25 April 2018
Necklace - COMPLETED!!!!
Size/Color | Small | Medium | Large | Huge | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | |
Aqua | - | - | - | - | - | - | C | - | - | - | - | - | - | |
Beige | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
Black | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
Coral | C | x | C | x | - | - | - | - | - | - | - | - | - | - |
Pink | - | - | - | - | - | - | - | - | - | - | - | x | x | - |
Smoke | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
White | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
Small Coral good color
Legend:
- - = Unknown
- '#' = Currently in slot
- X = Ruled out for that slot
- C = Correct Color, Correct Slot
-- Vegetable Macro for Tale 7 by thejanitor. -- -- Thanks to veggies.lua for the build button locations -- Updated 29-SEP-2017 by Silden to take into account UI changes that meant the windows would not close properly -- Updated 30-SEP-2017 by Silden to increase default values to cater for long veg names, such as Cabbage
dofile("common.inc") dofile("settings.inc")
WARNING=[[
THIS IS A BETA MACRO YOU ARE USING AT YOUR OWN RISK
You must be in the fully zoomed in top down F8 F8 F8 view, Alt+L to lock the camere once there.
In User Options -> Interface Options -> Menu You must DISABLE: "Right-Click Pins/Unpins a Menu"
You Must ENABLE: "Right-Click opens a Menu as Pinned"
You Must ENABLE: "Use the chat area instead of popups for many messages"
In Options -> One-Click and Related -> You must DISABLE: "Plant all crops where you stand"
In Options -> Video -> You must set: Shadow Quality and Time of Day lighting to the lowest possible.
Do not move once the macro is running and you must be standing on a tile with water available to refill.
Do not stand directly on or within planting distance of actual animated water.
]]
DEBUG=false
-- How many times to search for a plant / try open a bed window before giving up. SEARCH_RETRYS=10
-- How many pixels in a NxN grid to look for before MATCH_GRID_SIZE=5
-- These are the times in seconds it waits before watering a plant for a given stage. -- For example, plant A is planted at time 0. At time 2.8 seconds the macro queues up plant A to be watered, it then -- sleeps 0.2 seconds until FIRST_STAGE_WAIT time has passed before watering that plant an moving on.
-- If you encounter problems where plants are dieing at various stages it is ethier because these values are too low -- causing a plant to be watered 3+ times in a single stage before it grows. Or it is because they are too high and -- a plant is not recieving its water in time before regressing a stage. -- Finally if in the final harvest stage you get getting messages about running out of water then probably it is trying -- to harvest before it is ready and trying to rewater a plants 3rd stage. Increase the harvest wait to hopefully fix this.
-- TODO: Scale these based on global (and ideally local) teppy time. FIRST_STAGE_WAIT = 4 SECOND_STAGE_WAIT = 30 THIRD_STAGE_WAIT = 48 FORTH_STAGE_WAIT = 70 HARVEST_STAGE_WAIT = 83
STAGE_WAITS = { FIRST_STAGE_WAIT, SECOND_STAGE_WAIT, THIRD_STAGE_WAIT, FORTH_STAGE_WAIT, HARVEST_STAGE_WAIT }
-- How long to wait for the characters animations to stop at the end of each planting run. If this is too low -- then instead of clicking a newly placed plant the macro will hit your character. So if you see at the start of a new -- cycle the character menu being opened by the macro increase this value. END_OF_RUN_WAIT = 3000
-- Controls the size of each search box. The larger this is the slower the search phase which can break everything. SEARCH_BOX_SCALE = 1/10
MAX_PLANTS=12
RED = 0xFF2020ff BLACK = 0x000000ff WHITE = 0xFFFFFFff
LEEKS = "Horus' Grain" SEED_NAMES = {
"Tears of Sinai", "Green Leaf", "Bastet's Yielding", LEEKS, "Apep's Crop",
} SEED_TYPES = {
"Onions", "Carrots", "Cabbage", "Leeks", "Garlic",
}
-- Used to control the plant window placement and tiling. WINDOW_HEIGHT=120 -- Was 80 WINDOW_WIDTH=285 -- Was 220 WINDOW_OFFSET_X=150 WINDOW_OFFSET_Y=150
function doit()
while true do local config = makeReadOnly(getUserParams()) askForWindowAndSetupGlobals(config) gatherVeggies(config) end
end
function askForWindowAndSetupGlobals(config)
local min_jugs = config.num_waterings * config.num_plants * config.num_stages local min_seeds = config.num_plants + 8 local one = 'You will need ' .. min_jugs .. ' jugs of water and at minimum ' .. min_seeds .. ' seeds \n' local two = '\n Press Shift over ATITD window to continue.' askForWindow(one .. two) setupGlobals()
end
function setupGlobals()
NORTH = Vector:new{0,-1} SOUTH = Vector:new{0,1} WEST = Vector:new{-1,0} EAST = Vector:new{1,0} NORTH_WEST = NORTH + WEST NORTH_EAST = NORTH + EAST SOUTH_WEST = SOUTH + WEST SOUTH_EAST = SOUTH + EAST DOUBLE_SOUTH = SOUTH * 2 DOUBLE_NORTH = NORTH * 2 DOUBLE_WEST = WEST * 2 DOUBLE_EAST = EAST * 2
MOVE_BTNS = {} PLANT_LOCATIONS = {next=1} PlantLocation:new{direction_vector=NORTH, move_btn=Vector:new{59, 51}} PlantLocation:new{direction_vector=EAST ,move_btn=Vector:new{84, 74}} PlantLocation:new{direction_vector=SOUTH ,move_btn=Vector:new{60, 98}} PlantLocation:new{direction_vector=WEST ,move_btn=Vector:new{37, 75}} PlantLocation:new{direction_vector=NORTH, num_move_steps=2} PlantLocation:new{direction_vector=WEST, num_move_steps=2} PlantLocation:new{direction_vector=EAST, num_move_steps=2} PlantLocation:new{direction_vector=SOUTH, num_move_steps=2} PlantLocation:new{direction_vector=NORTH_EAST, move_btn=Vector:new{75, 62}} PlantLocation:new{direction_vector=NORTH_WEST, move_btn=Vector:new{45,60}} PlantLocation:new{direction_vector=SOUTH_EAST, move_btn=Vector:new{75, 91}} PlantLocation:new{direction_vector=SOUTH_WEST, move_btn=Vector:new{45, 87}} makeReadOnly(PLANT_LOCATIONS)
local mid = getScreenMiddle() ANIMATION_BOX = makeBox(mid.x - 60, mid.y - 50, 105, 85) ARM_BOX = makeBox(mid.x - 90, mid.y - 20, 80, 25) BUILD_BTN = Vector:new{31, 135}
end
PlantLocation={} function PlantLocation:new(o)
if o.num_move_steps then o.move_btn = PLANT_LOCATIONS[o.direction_vector].move_btn o.direction_vector = o.direction_vector * o.num_move_steps else o.num_move_steps = 1 end PLANT_LOCATIONS[o.direction_vector] = o PLANT_LOCATIONS[PLANT_LOCATIONS.next] = o PLANT_LOCATIONS.next = PLANT_LOCATIONS.next + 1 o.box = makeSearchBox(o.direction_vector) return newObject(PlantLocation, o, true)
end
function PlantLocation:move()
for step=1,self.num_move_steps do click(self.move_btn) end
end
function gatherVeggies(config)
local plants = Plants:new{num_plants=config.num_plants }
drawWater() for _=1,config.num_runs do local start = lsGetTimer()
checkBreak() --lsSleep(3000)
plants:iterate(Plant.plant, config) for round=1,config.num_stages+1 do plants:iterate(Plant.water, {stage_wait=STAGE_WAITS[round], num_waterings=config.num_waterings}) checkBreak() end
plants:iterate(Plant.close, config)
lsSleep(click_delay) drawWater() lsSleep(click_delay*5) checkBreak()
local stop = lsGetTimer() + END_OF_RUN_WAIT local total = math.floor((3600 / ((stop - start)/1000)) * config.num_plants * 9) -- default 3, currently 9 veggie yield with pyramids bonus sleepWithStatus(END_OF_RUN_WAIT, "Running at " .. total .. " veg per hour. ") end
end
-- Simple container object which constructs N plants and allows iteration over them. Plants={} function Plants:new(o)
for index=1,o.num_plants do local location = PLANT_LOCATIONS[index] self[index] = Plant:new{index=index, location=location} end return newObject(self,o,true)
end
function Plants:iterate(func, args)
for index=1,self.num_plants do func(self[index], args) end
end
Plant = {} function Plant:new(o)
o.window_pos = indexToWindowPos(o.index) return newObject(self,o)
end
function Plant:plant(config)
-- Take of a snapshot of the area in which we are guessing the plant will be placed before we actually create -- and place it. if not self.saved_plant_location then lsSleep(click_delay) self.beforePlantPixels = getBoxPixels(self.location.box) end
clickPlantButton(config.seed_name) self.location:move() local spot = getWaitSpotAt(BUILD_BTN) click(BUILD_BTN) self.plant_time = lsGetTimer() waitForChange(spot, click_delay*5) lsSleep(click_delay)
if not self.saved_plant_location then for _=1,SEARCH_RETRYS do if self:searchForPlant() then break end lsPrintln("Search retry for plant " .. self.index) lsSleep(tick_delay) end if not self.saved_plant_location then lsPrintln("Fail search for plant" .. self.index) end
end
self:openBedWindow(config.alternate_drag)
end
function Plant:searchForPlant()
lsPrintln("Searching for plant " .. self.index) return findChangedRow(self.location.box, self.beforePlantPixels, function (location, pixels) self.saved_plant_location = location self.saved_plant_pixels = pixels end )
end
function Plant:openBedWindow(alternate_drag)
if not self.saved_plant_location then lsPrintln("No Saved location for plant " .. self.index) return end
-- Wierd hacky thing, move the mouse to where the window will be and then safeClick the plant which causes -- the window to open instantly at the desired location and not where we clicked the plant. -- TODO: problably do something different as this is the only thing that takes mouse control from the user.
self.window_open = false local window_spot = getWaitSpotAt(self.window_pos + {5,5}) for _=1, SEARCH_RETRYS do if waitForPixelsAt(self.saved_plant_location, self.location.box, self.saved_plant_pixels) then if alternate_drag then local open_spot = getWaitSpotAt(self.saved_plant_location + {5,5}) click(self.saved_plant_location,true,true) if waitForChange(open_spot,click_delay*5) then lsSleep(click_delay) drag(self.saved_plant_location.x+5,self.saved_plant_location.y+5,self.window_pos.x,self.window_pos.y,click_delay*2) end else moveMouse(self.window_pos) click(self.saved_plant_location ,1) end
self.window_open = waitForChange(window_spot, click_delay*5) if self.window_open then break end end lsPrintln("Bed window open retry for plant " .. self.index) lsSleep(click_delay) end
if not self.window_open then lsPrintln("Bed window open fail for plant " .. self.index) end
end
function waitForPixelsAt(vector, box, pixels)
local match_size = MATCH_GRID_SIZE local half_match_size = math.floor(match_size/2) local box_location_x = vector.x - half_match_size local box_location_y = vector.y - half_match_size local match_box = makeBox(box_location_x, box_location_y, match_size, match_size) local all_same = true iterateBoxPixels(match_box, function(x,y,pixel) local pixels_x = box_location_x + x - box.left local pixels_y = box_location_y + y - box.top local old_pixel = pixels[pixels_y][pixels_x] local diff = calculatePixelDiffs(old_pixel, pixel) local diff_ok = diff[1] < 10 and diff[2] < 10 and diff[3] < 10 all_same = all_same and diff_ok end) return all_same
end
-- For a given plants index sleep until time_seconds has passed for that plant since it was planted.
function Plant:sleepUntil(time_seconds)
local sleepTime = time_seconds*1000 - (lsGetTimer() - self.plant_time); if sleepTime > 0 then sleepWithStatus(sleepTime, "Sleeping for " .. sleepTime) end
end
function Plant:clickWindow(offset,right_click,show)
if self.window_open then click(self.window_pos + offset,right_click,show) end
end
function Plant:water(args)
if not self.window_open then lsPrintln("Trying to water plant " .. self.index .. " which has no window open") return end
checkBreak() self:sleepUntil(args.stage_wait)
local search_box = makeBox(self.window_pos.x,self.window_pos.y-40,40,40) srReadScreen() local this_loc = srFindImageInRange("This.png", search_box.left, search_box.top, search_box.width, search_box.height, 4800) if not this_loc then lsPrintln("Didn't find This text for plant " .. self.index) self.window_open = false return end this_loc = Vector:new{x=this_loc[0],y=this_loc[1]+10}
click(this_loc) for _=1, args.num_waterings do lsSleep(click_delay) click(this_loc+{0,25}) click(this_loc) checkBreak() end
end
function Plant:close(config)
-- New UI changes affected the closing of finished windows. -- Now look for the UnPin image, and safeClick it closed if found. local search_box = makeBox(self.window_pos.x+65,self.window_pos.y-50,220,80) srReadScreen() local unpin_loc = srFindImageInRange("UnPin.png", search_box.left, search_box.top, search_box.width, search_box.height, 4800) if unpin_loc then
safeClick(unpin_loc[0],unpin_loc[1]);
else
lsPrintln("Didn't find unpin image for plant " .. self.index .. " looking left:" .. search_box.left .. ", top: " .. search_box.top .. ", width: " .. search_box.width .. ", height: " .. search_box.height .. ".");
return end
end
-- Create a table of direction string -> box. Each box is where we will search the plant placed for that given direction -- string. -- Full of janky hardcoded values. -- TODO: Make debuging this easier, figure out pixel scaling for different resolutions, get rid of magic numbers. function makeSearchBox(direction)
local xyWindowSize = srGetWindowSize() local search_size = math.floor(xyWindowSize[0] * SEARCH_BOX_SCALE) local mid = getScreenMiddle() local offset_mid = mid - {math.floor(search_size / 3), math.floor(search_size / 3) }
local top_left = offset_mid + direction*40 - Vector:new{20,20 }
local box = makeBox(top_left.x,top_left.y, search_size, search_size) box.direction = direction return box
end
function getScreenMiddle()
local xyWindowSize = srGetWindowSize() return Vector:new{math.floor(xyWindowSize[0]/2), math.floor(xyWindowSize[1]/2)}
end
-- Tiling method from Cinganjehoi's original bash script. Tried out the automato common ones but they are slow -- and broke sometimes? This is super simple and its not the end of the world if it breaks a little during a run. function indexToWindowPos(index)
local columns = getNumberWindowColumns() local x = WINDOW_WIDTH*((index-1) % columns) + WINDOW_OFFSET_X local y = WINDOW_HEIGHT*math.floor((index-1) / columns) + WINDOW_OFFSET_Y return Vector:new{x, y}
end
function getNumberWindowColumns()
local xyWindowSize = srGetWindowSize() local width = xyWindowSize[0] * 0.6 return math.floor(width / WINDOW_WIDTH);
end
function clickPlantButton(seed_name)
local build_menu_opened = false while not build_menu_opened do local plantButton = findText(seed_name) if plantButton then local spot = getWaitSpotAt(Vector:new{5,5}) clickText(plantButton, 1) build_menu_opened = waitForChange(spot,click_delay*5) else
lsPlaySound("fail.wav");
error("Text " .. seed_name .. " Not found.") end sleepWithStatus(tick_delay, "Planting...") --Retrying build menu open end
end
function getBoxPixels(box)
local pixels = {} iterateBoxPixels(box, function(x,y,pixel) pixels[y][x] = pixel end, function(y) pixels[y] = {} end ) return pixels
end
-- Finds a row of pixels which have changed in the current ReadScreen buffer compared to a given 2d array of pixels. function findChangedRow(box, pixels, func)
if DEBUG then debugShowBox(box) end
local changed = {} local new_pixels = {} iterateBoxPixels(box, function(x,y,pixel) changed[y][x] = pixels[y][x] ~= pixel new_pixels[y][x] = pixel end, function(y) changed[y] = {} new_pixels[y] = {} end )
local search_from_left = box.direction.x <= 0 local search_from_top = box.direction.y < 0
local y_start, y_end, y_inc = 0, math.floor(box.height/MATCH_GRID_SIZE)-1, 1 if not search_from_top then y_start, y_end, y_inc = y_end, y_start, -1 end local x_start, x_end, x_inc = 0, math.floor(box.width/MATCH_GRID_SIZE)-1, 1 if not search_from_left then x_start, x_end, x_inc = x_end, x_start, -1 end
for y=y_start,y_end,y_inc do for x=x_start,x_end,x_inc do local all_changed = true for j=0,MATCH_GRID_SIZE-1 do for k=0,MATCH_GRID_SIZE-1 do local changed_x = x*MATCH_GRID_SIZE+k local changed_y = y*MATCH_GRID_SIZE+j all_changed = all_changed and changed[changed_y][changed_x] end end if all_changed then local grid_centre_x = x*MATCH_GRID_SIZE + math.floor(MATCH_GRID_SIZE/2) local grid_centre_y = y*MATCH_GRID_SIZE + math.floor(MATCH_GRID_SIZE/2) func(Vector:new{box.left + grid_centre_x,box.top + grid_centre_y},new_pixels) return true end end end return false
end
function debugShowBox(box)
srSetMousePos(box.left, box.top) sleepWithStatus(2000, "TOP LEFT") srSetMousePos(box.right, box.bottom) sleepWithStatus(2000, "BOT RIGHT")
end
function inside(vector,box)
local x, y = vector.x, vector.y return (x >= box.left and x <= box.right) and (y >= box.top and y <= box.bottom)
end
function distanceCentre(vector)
local mid = getScreenMiddle() local delta = vector - mid
local dx = math.pow(delta.x,2) local dy = math.pow(delta.y,2)
return math.sqrt(dx + dy)
end
function iterateBoxPixels(box, xy_func, y_func)
srReadScreen()
for y=0,box.height,1 do if y_func then y_func(y) end for x=0, box.width do local pixel = srReadPixelFromBuffer(box.left + x, box.top + y) if xy_func(x,y,pixel) then return end end checkBreak() end
end
-- Used to place gui elements sucessively.
current_y = 0
-- How far off the left hand side to place gui elements.
X_PADDING = 5
function getUserParams()
local is_done = false local got_user_params = false local use_custom = readSetting("use_custom",false) local config = {alternate_drag=readSetting("alternate_drag")} config.seed_name = readSetting("seed_name","") config.num_waterings = readSetting("num_waterings",1) config.num_stages = readSetting("num_stages",1) local seed_index = readSetting("seed_index",1) local display_seed_names = {} for i=1,5 do display_seed_names[i] = SEED_NAMES[i] .. " (" .. SEED_TYPES[i] .. ")" end while not is_done do current_y = 10
if not got_user_params then local max_plants = MAX_PLANTS use_custom = lsCheckBox(X_PADDING, current_y, 10, WHITE, "Use custom seed?", use_custom) current_y = 45 if use_custom then config.seed_name = drawEditBox("seed_name", "Custom seed name? ", config.seed_name, false) config.num_waterings = drawNumberEditBox("num_waterings", "How many waterings per stage? ", config.num_waterings) config.num_stages = drawNumberEditBox("num_stages", "How many watering stages? ", config.num_stages) else seed_index = lsDropdown("seed_name", X_PADDING, current_y, 10, lsScreenX - 10, seed_index, display_seed_names) current_y = 85 end config.num_plants = drawNumberEditBox("num_plants", "How many to plant per run? Max " .. max_plants, 12) config.num_runs = drawNumberEditBox("num_runs", "How many runs? ", 20) config.click_delay = drawNumberEditBox("click_delay", "What should the click delay be? ", 100) config.alternate_drag = lsCheckBox(X_PADDING, current_y, 10, WHITE, "Alternate (slow) dragging?", config.alternate_drag) got_user_params = true for k,v in pairs(config) do got_user_params = got_user_params and v end got_user_params = got_user_params and drawBottomButton(lsScreenX - 5, "Next step") else drawWrappedText(WARNING, RED, X_PADDING, current_y)
is_done = drawBottomButton(lsScreenX - 5, "Start Script") end
if drawBottomButton(110, "Exit Script") then error "Script exited by user" end
lsDoFrame() lsSleep(10) end
writeSetting("seed_index",seed_index) writeSetting("alternate_drag",config.alternate_drag) writeSetting("use_custom",use_custom) writeSetting("seed_name",config.seed_name) writeSetting("num_waterings",config.num_waterings) writeSetting("num_stages",config.num_stages) config.num_plants = limitMaxPlants(config.num_plants) if not use_custom then config.seed_name = SEED_NAMES[seed_index] config.num_waterings = config.seed_name == LEEKS and 3 or 2 config.num_stages = 3 end click_delay = config.click_delay return config
end
function limitMaxPlants(user_supplied_max_num)
return math.min(12, user_supplied_max_num)
end
function drawNumberEditBox(key, text, default)
return drawEditBox(key, text, default, true)
end
function drawEditBox(key, text, default, validateNumber)
drawTextUsingCurrent(text, WHITE) local width = validateNumber and 50 or 200 local height = 30 local done, result = lsEditBox(key, X_PADDING, current_y, 0, width, height, 1.0, 1.0, BLACK, default) if validateNumber then result = tonumber(result) elseif result == "" then result = false end if not result then local error = validateNumber and "Please enter a valid number!" or "Enter text!" drawText(error, RED, X_PADDING + width + 5, current_y + 5) result = false end current_y = current_y + 35 return result
end
function drawTextUsingCurrent(text, colour)
drawText(text, colour, X_PADDING, current_y) current_y = current_y + 20
end function drawText(text, colour, x, y)
lsPrint(x, y, 10, 0.7, 0.7, colour, text)
end
function drawWrappedText(text, colour, x, y)
lsPrintWrapped(x, y, 10, lsScreenX-10, 0.6, 0.6, colour, text)
end
function drawBottomButton(xOffset, text)
return lsButtonText(lsScreenX - xOffset, lsScreenY - 30, z, 100, WHITE, text)
end
-- Simple immutable vector class Vector={} function Vector:new(o)
o.x = o.x or o[1] o.y = o.y or o[2] return newObject(self, o, true)
end
function Vector:__add(vector)
local x,y = Vector.getXY(vector) return Vector:new{self.x + x, self.y + y}
end
function Vector:__sub(vector)
local x,y = Vector.getXY(vector) return Vector:new{self.x - x, self.y - y}
end
function Vector:__div(divisor)
return Vector:new{self.x / divisor, self.y / divisor}
end
function Vector:__mul(multiplicand)
return Vector:new{self.x * multiplicand, self.y * multiplicand}
end
function Vector.getXY(vector)
return vector.x or vector[1], vector.y or vector[2]
end
function Vector:length()
return math.sqrt(self.x^2 + self.y^2)
end
function Vector:normalize()
return self / self:length()
end
function Vector:__tostring()
return "(" .. self.x .. ", " .. self.y .. ")"
end
function click(vector, right_click, show_mouse)
if show_mouse then srClickMouse(vector.x, vector.y, right_click) else safeClick(vector.x, vector.y, right_click) end lsSleep(click_delay)
end
function moveMouse(vector)
srSetMousePos(vector.x, vector.y) lsSleep(click_delay)
end
function getWaitSpotAt(vector)
return getWaitSpot(vector.x, vector.y)
end
-- Helper function used in an objects constructor to setup its metatable correctly allowing for basic inheritence. function newObject(class, o, read_only)
o = o or {} setmetatable(o, class) class.__index = class if read_only then makeReadOnly(o) end return o
end
function makeReadOnly(table)
local mt = getmetatable(table) if not mt then mt = {} if not table then print(debug.traceback()) end setmetatable(table,mt) end mt.__newindex = function(t,k,v) error("Attempt to update a read-only table", 2) end return table
end