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.

User:Augir/Necklace

From A Wiki in the Desert
< User:Augir
Revision as of 03:52, 25 April 2018 by Augir (talk | contribs)
Jump to navigation Jump to search

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