// ----------------------------------------------------------------------------- // -- libobjfind.simba // -- Written by: bonsai // ----------------------------------------------------------------------------- {$include_once srl-6/srl.simba} {$include_once bonsai/libupdater.simba} {$f-} const libobjfindVersion = '1.2'; libobjfindVerUrl = 'http://pastebin.com/raw.php?i=6nvaww82'; libobjfindCodeUrl = 'http://pastebin.com/raw.php?i=pGwsdnSt'; type TObjFilterStyle = (TOATPA, CLUSTER, SPLIT, NONE); TObjInclusiveAlg = (EXACT, RUNAWAY); TObjFilterSpec = record filterStyle: TObjFilterStyle; width: integer; height: integer; dist: integer; smallestPixels: integer; largestPixels: integer; end; TObjInclusiveSpec = record algorithm: TObjInclusiveAlg; minDist: integer; maxDist: integer; end; TObjColor = record color: integer; tolerance: integer; settings: TColorSettings; filterSpec: TObjFilterSpec; end; TObjColorArray = array of TObjColor; TObjFind = record name: string; colors: TObjColorArray; filterSpec: TObjFilterSpec; inclusiveSpec: TObjInclusiveSpec; lastFoundPoint: TPoint; lastFoundBox: TBox; end; const _NULL_FILTER_SPEC: TObjFilterSpec = [TObjFilterStyle.NONE, 0, 0, 0, 0]; _NULL_INCLUSIVE_SPEC: TObjInclusiveSpec = [TObjInclusiveAlg.EXACT, 0, 0]; ////////////////////////////////////////////////////////////////////////////////// ///// TObjFilterSpec routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFilterSpec.init() // // Initialize an empty split specification // ----------------------------------------------------------------------------- procedure TObjFilterSpec.init(); begin self := _NULL_FILTER_SPEC; end; // ----------------------------------------------------------------------------- // TObjFilterSpec.init(style: TObjFilterStyle; w: integer; // h: integer; countLow: integer; countHigh: integer) // // Initialize an split specification (w/h style) // ----------------------------------------------------------------------------- procedure TObjFilterSpec.init(style: TObjFilterStyle; w: integer; h: integer; countLow: integer; countHigh: integer); overload; begin self.filterStyle := style; self.width := w; self.height := h; self.dist := 0; self.smallestPixels := countLow; self.largestPixels := countHigh; end; // ----------------------------------------------------------------------------- // TObjFilterSpec.init(style: TObjFilterStyle; distance: integer; // countLow: integer; countHigh: integer) // // Initialize an split specification (dist style) // ----------------------------------------------------------------------------- procedure TObjFilterSpec.init(style: TObjFilterStyle; distance: integer; countLow: integer; countHigh: integer); overload; begin self.filterStyle := style; self.width := 0; self.height := 0; self.dist := distance; self.smallestPixels := countLow; self.largestPixels := countHigh; end; // ----------------------------------------------------------------------------- // TObjFilterSpec.isValid(): boolean; // // Sanity checks a split specification // ----------------------------------------------------------------------------- function TObjFilterSpec.isValid(): boolean; begin if ((self.dist = 0) and ((self.width = 0) or (self.height = 0))) then result := false else result := true; end; //////////////////////////////////////////////////////////////////////////////// // non-object routines that return a TObjFilterSpec. Can be used in code to // create TObjFilterSpec objects on the fly. //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // objFilterSpec() // // Create an empty split spec object // ----------------------------------------------------------------------------- function objFilterSpec(): TObjFilterSpec; begin result := _NULL_FILTER_SPEC; end; // ----------------------------------------------------------------------------- // objFilterSpec(style: TObjFilterStyle; w: integer; // h: integer; countLow: integer; countHigh: integer) // // Create a split spec object (w/h type). // ----------------------------------------------------------------------------- function objFilterSpec(style: TObjFilterStyle; w: integer; h: integer; countLow: integer; countHigh: integer): TObjFilterSpec; overload; var spec: TObjFilterspec; begin spec.init(style, w, h, countLow, countHigh); result := spec; end; // ----------------------------------------------------------------------------- // objFilterSpec(style: TObjFilterStyle; distance: integer; // countLow: integer; countHigh: integer) // // Create a split spec object (dist type). // ----------------------------------------------------------------------------- function objFilterSpec(style: TObjFilterStyle; distance: integer; countLow: integer; countHigh: integer): TObjFilterSpec; overload; var spec: TObjFilterspec; begin spec.init(style, distance, countLow, countHigh); result := spec; end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjInclusiveSpec routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjInclusiveSpec.init() // // Initialize an empty split specification // ----------------------------------------------------------------------------- procedure TObjInclusiveSpec.init(); begin self.algorithm := TObjInclusiveAlg.EXACT; self.minDist := 0; self.maxDist := 0; end; // ----------------------------------------------------------------------------- // TObjInclusiveSpec.init(alg: TObjInclusiveAlg; min: integer; max: integer) // // Initialize an inclusive specification // ----------------------------------------------------------------------------- procedure TObjInclusiveSpec.init(alg: TObjInclusiveAlg; min: integer; max: integer); overload; begin self.algorithm := alg; self.minDist := min; self.maxDist := min; end; //////////////////////////////////////////////////////////////////////////////// // non-object routines that return a TObjInclusiveSpec. Can be used in code to // create TObjInclusiveSpec objects on the fly. //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // objinclusiveSpec() // // Create an empty split spec object // ----------------------------------------------------------------------------- function objinclusiveSpec(): TObjInclusiveSpec; begin result.algorithm := TObjInclusiveAlg.EXACT; result.minDist := 0; result.maxDist := 0; end; // ----------------------------------------------------------------------------- // objinclusiveSpec(alg: TObjInclusiveAlg; min: integer; max: integer) // // Create an empty split spec object // ----------------------------------------------------------------------------- function objinclusiveSpec(alg: TObjInclusiveAlg; min: integer; max: integer): TObjInclusiveSpec; overload; begin result.algorithm := alg; result.minDist := min; result.maxDist := max; end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjColor routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjColor.init(col: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Initialize a pure color (CTS 0 with no tolerance) // ----------------------------------------------------------------------------- procedure TObjColor.init(col: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC); begin self.color := col; self.tolerance := 0; self.settings := colorSetting(0); self.filterSpec := spec; end; // ----------------------------------------------------------------------------- // TObjColor.init(col: integer; tol: integer; // spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Initialize a CTS 0 color with tolerance // ----------------------------------------------------------------------------- procedure TObjColor.init(col: integer; tol: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload; begin self.color := col; self.tolerance := tol; self.settings := colorSetting(0); self.filterSpec := spec; end; // ----------------------------------------------------------------------------- // TObjColor.init(col: integer; tol: integer; hue: extended; // saturation: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Initialize a CTS 2 color // ----------------------------------------------------------------------------- procedure TObjColor.init(col: integer; tol: integer; hue: extended; saturation: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload; begin self.color := col; self.tolerance := tol; self.settings := colorSetting(2, hue, saturation); self.filterSpec := spec; end; // ----------------------------------------------------------------------------- // TObjColor.init(col: integer; tol: integer; sensitivity: extended; // spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Initialize a CTS 3 color // ----------------------------------------------------------------------------- procedure TObjColor.init(col: integer; tol: integer; sensitivity: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload; begin self.color := col; self.tolerance := tol; self.settings := colorSetting(3, sensitivity); self.filterSpec := spec; end; // ----------------------------------------------------------------------------- // TObjColor.init(col: integer; tol: integer; cs: TcolorSettings; // spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Initialize a color with user specified tolerance and TcolorSettings // ----------------------------------------------------------------------------- procedure TObjColor.init(col: integer; tol: integer; cs: TcolorSettings; spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload; begin self.color := col; self.tolerance := tol; self.settings := cs; self.filterSpec := spec; end; //////////////////////////////////////////////////////////////////////////////// // non-object routines that return a TObjColor. Can be used in code to // create TObjColor objects on the fly. //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // objColor(col: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Create a CTS 0 color object with tolerance // ----------------------------------------------------------------------------- function objColor(col: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; var obj : TObjColor; begin obj.init(col, spec); result := obj; end; // ----------------------------------------------------------------------------- // objColor(col: integer; tol: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Create a CTS 0 color object with tolerance // ----------------------------------------------------------------------------- function objColor(col: integer; tol: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload; var obj : TObjColor; begin obj.init(col, tol, spec); result := obj; end; // ----------------------------------------------------------------------------- // objColor(col: integer; tol: integer; hue: extended; saturation: extended; // spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Create a CTS 2 color object // ----------------------------------------------------------------------------- function objColor(col: integer; tol: integer; hue: extended; saturation: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload; var obj : TObjColor; begin obj.init(col, tol, hue, saturation, spec); result := obj; end; // ----------------------------------------------------------------------------- // objColor(col: integer; tol: integer; sensitivity: extended; // spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Create a CTS 3 color object // ----------------------------------------------------------------------------- function objColor(col: integer; tol: integer; sensitivity: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload; var obj : TObjColor; begin obj.init(col, tol, sensitivity, spec); result := obj; end; // ----------------------------------------------------------------------------- // objColor(col: integer; tol: integer; cs: TcolorSettings; // spec: TObjFilterSpec = _NULL_FILTER_SPEC) // // Create a color object with the specified TcolorSettings // ----------------------------------------------------------------------------- // fully specified Tcolorsettings function objColor(col: integer; tol: integer; cs: TcolorSettings; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload; var obj : TObjColor; begin obj.init(col, tol, cs, spec); result := obj; end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjFind routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFind.init(colArr: TObjColorArray; spec: TObjFilterSpec = _NULL_FILTER_SPEC; // inclSpec: TobjinclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); // // Initialize an object with user specified color array // ----------------------------------------------------------------------------- procedure TObjFind.init(colArr: TObjColorArray; spec: TObjFilterSpec = _NULL_FILTER_SPEC; inclSpec: TobjinclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); begin self.name := nameStr; setLength(self.colors, length(colArr)); self.colors := colArr; self.filterSpec := spec; self.inclusiveSpec := inclSpec; self.lastFoundPoint := Point(0, 0); self.lastFoundBox := intToBox(0, 0, 0, 0); end; // ----------------------------------------------------------------------------- // TObjFind.init(spec: TObjFilterSpec = _NULL_FILTER_SPEC; // inclSpec: TobjinclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); // // Initialize an object with no colors loaded // ----------------------------------------------------------------------------- procedure TObjFind.init(spec: TObjFilterSpec = _NULL_FILTER_SPEC; inclSpec: TobjinclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); overload; begin self.name := nameStr; setLength(self.colors, 0); self.filterSpec := spec; self.inclusiveSpec := inclSpec; self.lastFoundPoint := Point(0, 0); self.lastFoundBox := intToBox(0, 0, 0, 0); end; // ----------------------------------------------------------------------------- // TObjFind.init(col: TObjColor; spec: TObjFilterSpec = _NULL_FILTER_SPEC; // nameStr: string = '') // // Initialize an object with a single color // ----------------------------------------------------------------------------- procedure TObjFind.init(col: TObjColor; spec: TObjFilterSpec = _NULL_FILTER_SPEC; inclSpec: TobjinclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); overload; begin self.init([col], spec, inclSpec, nameStr); end; // ----------------------------------------------------------------------------- // TObjFind.addColor(col: TObjColor) // // Add a color to an object // ----------------------------------------------------------------------------- procedure TObjFind.addColor(col: TObjColor); begin setLength(self.colors, length(self.colors)+1); self.colors[high(self.colors)] := col; end; // ----------------------------------------------------------------------------- // TObjFind.setColors(colArr: TObjColorArray) // // Set all the colors for an object from a color array // ----------------------------------------------------------------------------- procedure TObjFind.setColors(colArr: TObjColorArray); begin setLength(self.colors, length(colArr)); self.colors := colArr; end; // ----------------------------------------------------------------------------- // TObjFind.getColors() // // Returns the current array of colors for the object // ----------------------------------------------------------------------------- function TObjFind.getColors(): TObjColorArray; begin result := self.colors; end; // ----------------------------------------------------------------------------- // TObjFind.setFilterSpec(spec: TObjFilterSpec) // // Set the split specification for the entire object // ----------------------------------------------------------------------------- procedure TObjFind.setFilterSpec(spec: TObjFilterSpec); begin self.filterSpec := spec; end; // ----------------------------------------------------------------------------- // TObjFind.getFilterSpec(spec: TObjFilterSpec) // // Set the split specification for the entire object // ----------------------------------------------------------------------------- function TObjFind.getFilterSpec(): TObjFilterSpec; begin result := self.filterSpec; end; // ----------------------------------------------------------------------------- // TObjFind.setInclusiveSpec(iSpec: TObjInclusiveSpec) // // Add an inclusive spec to an object // ----------------------------------------------------------------------------- procedure TObjFind.setInclusiveSpec(iSpec: TObjInclusiveSpec); begin self.inclusiveSpec := iSpec; end; // ----------------------------------------------------------------------------- // TObjFind.getInclusiveSpec(spec: TObjFilterSpec) // // Set the split specification for the entire object // ----------------------------------------------------------------------------- function TObjFind.getInclusiveSpec(): TObjInclusiveSpec; begin result := self.inclusiveSpec; end; // ----------------------------------------------------------------------------- // TObjFind.getLastPoint() // // Returns the last point the object was found at // ----------------------------------------------------------------------------- function TObjFind.getLastPoint(): TPoint; begin result := self.lastFoundPoint; end; // ----------------------------------------------------------------------------- // TObjFind.getLastBox() // // Returns the last box the object was found in // ----------------------------------------------------------------------------- function TObjFind.getLastBox(): TBox; begin result := self.lastFoundBox; end; ////////////////////////////////////////////////////////////////////////////////// ///// Internal routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFind._debugATPA(prefix: string; atpa: T2DPointArray); // // Prints info to debug screen for an atpa // ----------------------------------------------------------------------------- procedure TObjFind._debugATPA(prefix: string; atpa: T2DPointArray); var i: integer; begin {$IFDEF TOBJ_DEBUG} writeln(prefix, ' atpa length = ', length(atpa)); if length(atpa) = 0 then exit; for i := 0 to high(atpa) do writeln(prefix, ' .. sub-tpa length = ', length(atpa[i])); {$ENDIF} end; // ----------------------------------------------------------------------------- // TObjFind._doFilterATPA(tpa: TPointArray; spec: TObjFilterSpec; // functionName: string; color: integer) // // Splits and filters a tpa (returning it in an array of tpa form). // ----------------------------------------------------------------------------- function TObjFind._doFilterATPA(tpa: TPointArray; spec: TObjFilterSpec; functionName: string; color: integer): T2DPointArray; var atpa: T2DPointArray; begin if (spec.filterStyle <> TObjFilterStyle.NONE) then begin if (not spec.isValid()) then begin writeln('WARNING: ', functionName, ' asked to split but filterSpec was invalid'); setLength(result, 1); result[0] := tpa; exit; end; case spec.filterStyle of TObjFilterStyle.TOATPA: begin if (spec.dist > 0) then atpa := tpa.toATPA(spec.dist) else if ((spec.width > 0) and (spec.height > 0)) then atpa := tpa.toATPA(spec.width, spec.height); end; TObjFilterStyle.CLUSTER: begin if (spec.dist > 0) then atpa := tpa.cluster(spec.dist) else if ((spec.width > 0) and (spec.height > 0)) then atpa := tpa.cluster(spec.width, spec.height); end; TObjFilterStyle.SPLIT: begin if (spec.dist > 0) then atpa := tpa.split(spec.dist) else if ((spec.width > 0) and (spec.height > 0)) then atpa := tpa.split(spec.width, spec.height); end; end; // case {$IFDEF TOBJ_DEBUG} self._debugATPA('DEBUG: ' + functionName +' color ' + inttoStr(color) + ' pre-filter ', atpa); {$ENDIF} if (length(atpa) > 0) then begin if (spec.smallestPixels > 0) then atpa.filterBetween(0, spec.smallestPixels-1); if ((spec.largestPixels > 0) and (spec.largestPixels < maxInt)) then atpa.filterBetween(spec.largestPixels+1, maxInt); end; {$IFDEF TOBJ_DEBUG} self._debugATPA('DEBUG: ' + functionName + ' color ' + inttoStr(color) + ' post-filter ', atpa); {$ENDIF} end; // if filterStyle<>NONE result := atpa; end; // ----------------------------------------------------------------------------- // TObjFind._doFilter(tpa: TPointArray; spec: TObjFilterSpec; // functionName: string; color: integer) // // Splits and filters a tpa and returns results as a single tpa // ----------------------------------------------------------------------------- function TObjFind._doFilter(tpa: TPointArray; spec: TObjFilterSpec; functionName: string; color: integer): TPointArray; var atpa: T2DPointArray; begin atpa := self._doFilterATPA(tpa, spec, functionName, color); result := atpa.merge(); end; // ----------------------------------------------------------------------------- // TObjFind._doFilterInclusiveExact(allPoints: T2DPointArray) // // Filters points so the only remaining ones are where all colors are in the // specified distance in the inclusiveSpec // ----------------------------------------------------------------------------- function TobjFind._doFilterInclusiveExact(allPoints: T2DPointArray) : T2DPointArray; var base, compareTo, basePoint, i: integer; highAllPoints, highBase: integer; foundOne: array of boolean; foundAll: boolean; begin if ((self.inclusiveSpec.minDist <= 0) and (self.inclusiveSpec.maxDist <= 0)) then begin result := allPoints.copy(); exit; end; highAllPoints := high(allPoints); // initialize things setLength(foundOne, length(allPoints)); setLength(result, length(allPoints)); for i := 0 to highAllPoints do setLength(result[i], 0); for base := 0 to highAllPoints do begin {$IFDEF TOBJ_DEBUG} writeln('TobjFind._doFilterInclusive: Processing array ', base); {$ENDIF} foundOne[base] := true; // we can find our own point within distance :) highBase := high(allPoints[base]); for basePoint := 0 to highBase do begin for compareTo := 0 to highAllPoints do begin // don't compare to the same color, only the others if (compareTo = base) then continue; foundOne[compareTo] := NearbyPointInArray(allPoints[base][basePoint], self.inclusiveSpec.maxDist, allPoints[compareTo]); end; // all other colors have been looked at, did they all match? // if so, add this point to the return list. foundAll := true; for i := 0 to highAllPoints do if (not foundOne[i]) then begin foundAll := false; break; end; if (foundAll) then begin if (length(result[base]) = 0) then begin setLength(result[base], 1); result[base][0] := allPoints[base][basePoint]; end else result[base].append(allPoints[base][basePoint]); end; end; // for basePoint end; // for base {$IFDEF TOBJ_DEBUG} writeln('TobjFind._doFilterInclusive: processing complete. len result=', length(result)); {$ENDIF} end; // ----------------------------------------------------------------------------- // TObjFind._doFilterInclusiveRunaway(aPoints: T2DPointArray) // // Splits and filters an atpa using inclusive logic. This algorithm is // faster than the EXACT one but does not return 100% of the points // // Algorithm taken (with permission) from RunAway's thread: // http://villavu.com/forum/showthread.php?t=82346&highlight=FindTPACluster // ----------------------------------------------------------------------------- function TObjFind._doFilterInclusiveRunaway(aPoints: T2DPointArray): T2DPointArray; var res: TPointArray; dist: array of Extended; which: array of Integer; hDist, hPoints, hColors: Integer; d, t: Integer; w, ww: Integer; r, newClusters: Integer; i, ii, iii: Integer; match, count: Integer; duplicate: Boolean; begin SetLength(dist, Length(aPoints) - 1); SetLength(which, Length(aPoints) - 1); hPoints := High(aPoints); hDist := High(dist); for i := 0 to hPoints do begin {$IFDEF TOBJ_DEBUG} writeln('Sorting: array '+IntToStr(i + 1)+' of '+IntToStr(hPoints + 1)+'...'); newClusters := 0; {$ENDIF} count := 0; for w := 0 to hDist do begin if (hPoints - w = hPoints - i) or (hPoints - w < hPoints - i) then which[w] := w + 1 else which[w] := w; end; for ii := 0 to high(aPoints[i]) do begin match := 0; for iii := 0 to hDist do begin if (count > high(aPoints[which[iii]])) then begin inc(count); Break; //Continue; end; dist[iii] := distance(aPoints[i][ii].x, aPoints[i][ii].y, aPoints[which[iii]][count].x, aPoints[which[iii]][count].y); if (dist[iii] >= self.inclusiveSpec.minDist) and (dist[iii] <= self.inclusiveSpec.maxDist) then inc(match) else begin inc(count); Break; end; end; if (match >= hPoints) then begin setLength(res, Length(aPoints)); res[0] := aPoints[i][ii]; for ww := 0 to hDist do res[ww + 1] := aPoints[which[ww]][count]; sortTPAByX(res, True); if (length(result) > 1) then begin duplicate := false; for d := 0 to high(result) do begin if (result[d] = res) then duplicate := True; end; if duplicate then begin inc(count); continue; end; end; setLength(result, r + 1); result[r] := res; inc(r); {$IFDEF TOBJ_DEBUG} inc(newClusters); {$ENDIF} res := []; end; inc(count); end; {$IFDEF TOBJ_DEBUG} writeln('Found: '+intToStr(newClusters)+' new cluster(s)'); {$ENDIF} end; end; // ----------------------------------------------------------------------------- // TObjFind._doFilterInclusive(allPoints: T2DPointArray) // // Filters points so the only remaining ones are where all colors are in the // specified distance in the inclusiveSpec. This function parses out the // request to the proper algorithm specific function. // ----------------------------------------------------------------------------- function TobjFind._doFilterInclusive(allPoints: T2DPointArray) : T2DPointArray; begin if ((self.inclusiveSpec.minDist <= 0) and (self.inclusiveSpec.maxDist <= 0)) then begin result := allPoints.copy(); exit; end; case (self.inclusiveSpec.algorithm) of TObjInclusiveAlg.EXACT: result := self._doFilterInclusiveExact(allPoints); TObjInclusiveAlg.RUNAWAY: result := self._doFilterInclusiveRunaway(allPoints); end; end; // ----------------------------------------------------------------------------- // TObjFind._validateBoxes(var searchBox : TBox; var sortFrom: TPoint) // // Ensures that searchBox and sortFrom are not out of bounds of the screen. // Fills in default values if they have not been set (mainscreen when using // SMART, full screen when not). Default for sortFrom is the center of // searchBox. // ----------------------------------------------------------------------------- procedure TObjFind._validateBoxes(var searchBox : TBox; var sortFrom: TPoint); var cw, ch: integer; begin GetClientDimensions(cw, ch); if (searchBox.equals(intToBox(-1,-1,-1,-1))) then begin {$IFDEF SMART} searchBox := mainscreen.getBounds(); {$ELSE} searchBox := intToBox(0, 0, cw-1, ch-1); {$ENDIF} end; if (searchBox.x1 < 0) then searchBox.x1 := 0; if (searchBox.y1 < 0) then searchBox.y1 := 0; if (searchBox.x2 < 0) then searchBox.x2 := 0; if (searchBox.y2 < 0) then searchBox.y2 := 0; if (searchBox.x1 > cw) then searchBox.x1 := cw; if (searchBox.y1 > ch) then searchBox.y1 := ch; if (searchBox.x2 > cw) then searchBox.x2 := cw; if (searchBox.y2 > ch) then searchBox.y2 := ch; if (sortFrom.equals(point(-1,-1))) then sortFrom := searchBox.getMiddle(); if (sortFrom.x < 0) then sortFrom.x := 0; if (sortFrom.y < 0) then sortFrom.y := 0; if (sortFrom.x > cw) then sortFrom.x := cw; if (sortFrom.y > ch) then sortFrom.y := ch; end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjFind findColorsATPA routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFind.findColorsATPA(searchBox: TBox = [-1,-1,-1,-1]; // sortFrom: TPoint = [-1,-1]) // // Returns a T2DPointArray containing all the colors from the object // inside the searchbox. Each subarray is the points for a single color in // the colorarray. Sorts each sub-array from sortFrom. Filtering is done // at the color and object level if the appropriate filters were placed on // the object. // // If an inclusive spec is present all colors will be merged into a single // result before object filtering, so the atpa returned does not reflect one // array per color as one might expect. // ----------------------------------------------------------------------------- function TObjFind.findColorsATPA(searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): T2DPointArray; var len, i, bmp, bmpW, bmpH: integer; saveCS, cs: TColorSettings; found: boolean; tpa: TPointArray; atpa: T2DPointArray; mbmp: TMufasaBitmap; begin {$IFDEF TOBJ_DEBUG} writeln('In TObjFind.findColorsATPA'); GetClientDimensions(bmpW,bmpH); bmp := BitmapFromClient(0, 0, bmpW-1, bmpH-1); mbmp := getmufasabitmap(bmp); {$ENDIF} self._validateBoxes(searchBox, sortFrom); len := length(self.colors); setLength(result, len); if (len = 0) then exit; for i := 0 to (len-1) do with self.colors[i] do begin saveCS.retrieve(); settings.apply(); found := FindColorsTolerance(result[i], color, searchBox.x1, searchBox.y1, searchBox.x2, searchBox.y2, tolerance); saveCS.apply(); {$IFDEF TOBJ_DEBUG} if (not found) then writeln('WARNING: TObjFind.findColorsATPA returned no matches for color ', color) else writeln('DEBUG: TObj.findColorsATPA matched ', length(result[i]), ' points for color ', color); {$ENDIF} if (not found) then continue; // no need for more processing if (filterSpec.filterStyle <> TObjFilterStyle.NONE) then begin result[i] := _doFilter(result[i], self.colors[i].filterSpec, 'TObjFind.findColorsATPA', color); end; {$IFDEF TOBJ_DEBUG} writeln('DEBUG: TObj.findColorsATPA after split/filter there are ', length(result[i]), ' points for color ', color); {$ENDIF} result[i].sortFromPoint(sortFrom); end; // with self.colors[i] result := self._doFilterInclusive(result); if ((self.inclusiveSpec.minDist > 0) or (self.inclusiveSpec.maxDist > 0)) then begin // mash it all together like it was one color then run it through obj filter tpa := result.merge(); if (self.filterSpec.isValid()) then result := self._doFilterATPA(tpa, self.filterSpec, 'TObj.findColorsATPA', 9999999) else begin setLength(result, 1); result[0] := tpa.copy(); result[0].sortFromPoint(sortFrom); end; end else begin // perform object level split/filter if (self.filterSpec.isValid()) then begin for i := 0 to high(result) do begin result[i] := self._doFilter(result[i], self.filterSpec, 'TObj.findColorsATPA', self.colors[i].color); result[i].sortFromPoint(sortFrom); {$IFDEF TOBJ_DEBUG} writeln('DEBUG: TObj.findColorsATPA after object split/filter there are ', length(result[i]), ' points for color ', self.colors[i].color); {$ENDIF} end; end; end; {$IFDEF TOBJ_DEBUG} for i := 0 to high(result) do begin mbmp.debugTPA(result[i], true); writeln('DEBUG: TObj.findColorsATPA after all processing there are ', length(result[i]), ' points for color ', i); end; mbmp.debugATPA(result); DisplayDebugImgWindow(bmpW,bmpH); DrawBitmapDebugImg(bmp); FreeBitmap(bmp); {$ENDIF} end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjFind findColors routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFind.findColors(searchBox: TBox = [-1,-1,-1,-1]; // sortFrom: TPoint = [-1,-1]) // // Returns a TPointArray containing all the colors from the object // inside the searchbox. Sorts results from sortFrom. Filters colors // and the object as a whole if they were set on the object. // ----------------------------------------------------------------------------- function TObjFind.findColors(searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TPointArray; var atpa: T2DPointArray; tpa: TPointArray; bmp, bmpW, bmpH: integer; mbmp: TMufasaBitmap; begin {$IFDEF TOBJ_DEBUG} writeln('In TObjFind.findColors'); GetClientDimensions(bmpW,bmpH); bmp := BitmapFromClient(0, 0, bmpW-1, bmpH-1); mbmp := getmufasabitmap(bmp); {$ENDIF} self._validateBoxes(searchBox, sortFrom); atpa := self.findColorsATPA(searchBox, sortFrom); tpa := atpa.merge(); tpa.sortFromPoint(sortFrom); result := tpa; {$IFDEF TOBJ_DEBUG} mbmp.debugTPA(tpa, true); DisplayDebugImgWindow(bmpW,bmpH); DrawBitmapDebugImg(bmp); FreeBitmap(bmp); {$ENDIF} end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjFind findBoxes routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFind.findBoxes(searchBox: TBox = [-1,-1,-1,-1]; // sortFrom: TPoint = [-1,-1] // // Returns an array of boxes outlining the objects found. The splitting is // performed with the object filter, which is required if calling this // function. // ----------------------------------------------------------------------------- function TObjFind.findBoxes(searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TBoxArray; var tpa: TPointArray; atpa: T2DPointArray; i: integer; bmp, bmpW, bmpH: integer; mbmp: TMufasaBitmap; begin {$IFDEF TOBJ_DEBUG} writeln('In TObjFind.findBoxes'); {$ENDIF} if (self.filterSpec.filterStyle = TObjFilterStyle.NONE) then begin writeln('ERROR: TObjFind.findBoxes: Cannot find boxes since filterStyle=NONE'); setLength(result, 0); exit; end; self._validateBoxes(searchBox, sortFrom); atpa := self.findColorsATPA(searchBox, sortFrom); {$IFDEF TOBJ_DEBUG} GetClientDimensions(bmpW,bmpH); bmp := BitmapFromClient(0, 0, bmpW-1, bmpH-1); mbmp := getmufasabitmap(bmp); {$ENDIF} // filtering should already be done, we're doing it twice. could make // this more efficient by duplicating some of the code from findColorsATPA // instead of calling it above. // tpa := atpa.merge(); atpa := self._doFilterATPA(tpa, self.filterSpec, 'TObjFind.findBoxes', 9999999); setLength(result, length(atpa)); if (length(atpa) = 0) then exit; for i := 0 to high(atpa) do atpa[i].sortFromPoint(sortFrom); atpa.sortFromFirstPoint(sortFrom); for i := 0 to high(atpa) do begin result[i] := atpa[i].getBounds(); {$IFDEF TOBJ_DEBUG} mbmp.drawBox(result[i]); {$ENDIF} end; self.lastFoundBox := result[0]; self.lastFoundPoint := result[0].getMiddle(); {$IFDEF TOBJ_DEBUG} DisplayDebugImgWindow(bmpW,bmpH); DrawBitmapDebugImg(bmp); FreeBitmap(bmp); {$ENDIF} end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjFind findCenters routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFind.findCenters(searchBox: TBox = [-1,-1,-1,-1]; // sortFrom: TPoint = [-1,-1] // // Uses the findBoxes function and returns the center of the boxes it found. // ----------------------------------------------------------------------------- function TObjFind.findCenters(searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TPointArray; var objBoxes: TBoxArray; i, len: integer; bmp, bmpW, bmpH: integer; mbmp: TMufasaBitmap; begin {$IFDEF TOBJ_DEBUG} writeln('In TObjFind.findCenters'); GetClientDimensions(bmpW,bmpH); bmp := BitmapFromClient(0, 0, bmpW-1, bmpH-1); mbmp := getmufasabitmap(bmp); {$ENDIF} self._validateBoxes(searchBox, sortFrom); objBoxes := self.findBoxes(searchBox, sortFrom); len := length(objBoxes); setLength(result, len); if (len = 0) then exit; for i := 0 to len-1 do begin result[i] := objBoxes[i].getMiddle(); {$IFDEF TOBJ_DEBUG} mbmp.drawCross(result[i], 10, clRed); {$ENDIF} end; {$IFDEF TOBJ_DEBUG} DisplayDebugImgWindow(bmpW,bmpH); DrawBitmapDebugImg(bmp); FreeBitmap(bmp); {$ENDIF} end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjFind choose routines ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFind.choose(searchBox: TBox; sortFrom: TPoint; matchUptext: TStringArray; // excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray; // byBox: boolean = false; pointVariance: integer = 5) // // Locates all of the object boxes or centerpoints, depending on the byBox flag. // Moves the mouse to them, validates if they match uptext. When a match is found // it performs the requested mouseStyle on it. If mouseStyle is // MOUSE_RIGHT it will right click and select one of the given choices from // the menu. // ----------------------------------------------------------------------------- function TObjFind.choose(searchBox: TBox; sortFrom: TPoint; matchUptext: TStringArray; excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray; byBox: boolean = false; pointVariance: integer = 5): boolean; var objBoxes: TBoxArray; objPoints: TPointArray; i, j, hi: integer; moText: string; exclude: boolean; begin result := false; if ((mouseStyle <> MOUSE_MOVE) and (mouseStyle <> MOUSE_LEFT) and (mouseStyle <> MOUSE_RIGHT)) then exit; // why did they call this function? :) self._validateBoxes(searchBox, sortFrom); if (byBox) then begin objBoxes := self.findBoxes(searchBox, sortFrom); hi := high(objBoxes); end else begin objPoints := self.findCenters(searchBox, sortFrom); hi := high(objPoints); end; {$IFDEF DEBUG_ON} writeln('TObjFind.choose: found ', hi+1, ' possible objects'); {$ENDIF} for i := 0 to hi do begin if (byBox) then objBoxes[i].mouse(MOUSE_MOVE) else mouse(objPoints[i].rand(pointVariance), MOUSE_MOVE); {$IFDEF DEBUG_ON} writeln('TObjFind.choose: mouse moved to object ', i); {$ENDIF} wait(randomRange(35,55)); moText := getMouseOverText(); {$IFDEF DEBUG_ON} writeln('TObjFind.choose: examining mouseText=', moText); {$ENDIF} exclude := false; for j := 0 to high(excludeUptext) do if (pos(excludeUptext[j], moText) > 0) then begin {$IFDEF DEBUG_ON} writeln('TObjFind.choose: ', moText, ' excluded.'); {$ENDIF} exclude := true; break; end; if (not exclude) then for j := 0 to high(matchUptext) do if (pos(matchUptext[j], moText) > 0) then // we have a match begin {$IFDEF DEBUG_ON} writeln('TObjFind.choose: ', moText, ' matched; will click object'); {$ENDIF} case mouseStyle of MOUSE_LEFT: begin fastClick(mouseStyle); result := true; end; MOUSE_RIGHT: begin fastClick(mouseStyle); result := chooseoption.select(choices, randomRange(800,1400)); if chooseoption.isOpen() then chooseOption.close(); {$IFDEF DEBUG_ON} writeln('TObjFind.choose: chooseOption called on object, result=', result); {$ENDIF} end; MOUSE_NONE, MOUSE_MOVE: result := true; end; // case if (result) then exit; end; // if match end; // for i end; // ----------------------------------------------------------------------------- // TObjFind.choose(searchBox: TBox; matchUptext: TStringArray; // excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray; // byBox: boolean = false; pointVariance:integer = 5) // // Variation with no sortFrom specified // ----------------------------------------------------------------------------- function TObjFind.choose(searchBox: TBox; matchUptext: TStringArray; excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray; byBox: boolean = false; pointVariance:integer = 5): boolean; overload; begin result := self.choose(searchBox, mainscreen.playerpoint, matchUptext, excludeUptext, mouseStyle, choices, byBox, pointVariance); end; // ----------------------------------------------------------------------------- // TObjFind.choose(matchUptext: TStringArray; excludeUptext: TStringArray; // mouseStyle: integer; choices: TStringArray; byBox: boolean = false; // pointVariance: integer = 5) // // Variation with no searchBox or sortFrom specified // ----------------------------------------------------------------------------- function TObjFind.choose(matchUptext: TStringArray; excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray; byBox: boolean = false; pointVariance: integer = 5): boolean; overload; begin result := self.choose(mainscreen.getBounds(), mainscreen.playerpoint, matchUptext, excludeUptext, mouseStyle, choices, byBox, pointVariance); end; ////////////////////////////////////////////////////////////////////////////////// ///// TObjFind.findWithin functions ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // TObjFind.findWithinATPA(enclosingObj: TObjFind; // searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]) // // Finds one object inside of another. Returns the found object in ATPA form // ----------------------------------------------------------------------------- function TObjFind.findWithinATPA(enclosingObj: TObjFind; searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): T2DPointArray; var objBoxes: TBoxArray; i, j, len: integer; bmp, bmpW, bmpH: integer; mbmp: TMufasaBitmap; atpa: T2DPointArray; tpa: TPointArray; begin {$IFDEF TOBJ_DEBUG} writeln('In TObjFind.findWithinATPA'); GetClientDimensions(bmpW,bmpH); bmp := BitmapFromClient(0, 0, bmpW-1, bmpH-1); mbmp := getmufasabitmap(bmp); {$ENDIF} self._validateBoxes(searchBox, sortFrom); objBoxes := enclosingObj.findBoxes(searchBox, sortFrom); len := length(objBoxes); if (len = 0) then exit; for i := 0 to len-1 do begin atpa := self.findColorsATPA(objBoxes[i], objBoxes[i].getMiddle()); // merge into results for j := 0 to high(atpa) do begin if (length(result) < j+1) then setLength(result, j+1); result[j].combine(atpa[j]); end; end; // in the case of inclusive searching, we want to combine the data // and run it through the object filter to get coherent objects to // return. if (((self.inclusiveSpec.minDist > 0) or (self.inclusiveSpec.maxDist > 0)) and (self.filterSpec.isValid())) then begin // mash it all together like it was one color then run it through obj filter tpa := result.merge(); result := self._doFilterATPA(tpa, self.filterSpec, 'TObj.findColorsATPA', 9999999) end {$IFDEF TOBJ_DEBUG} mbmp.debugATPA(result); DisplayDebugImgWindow(bmpW,bmpH); DrawBitmapDebugImg(bmp); FreeBitmap(bmp); {$ENDIF} end; // ----------------------------------------------------------------------------- // TObjFind.findWithinColors(enclosingObj: TObjFind; // searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]) // // Finds one object inside of another. Returns the found object in TPA form // ----------------------------------------------------------------------------- function TObjFind.findWithinColors(enclosingObj: TObjFind; searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TPointArray; var atpa: T2DPointArray; tpa: TPointArray; bmp, bmpW, bmpH: integer; mbmp: TMufasaBitmap; begin {$IFDEF TOBJ_DEBUG} writeln('In TObjFind.findWithinColors'); GetClientDimensions(bmpW,bmpH); bmp := BitmapFromClient(0, 0, bmpW-1, bmpH-1); mbmp := getmufasabitmap(bmp); {$ENDIF} self._validateBoxes(searchBox, sortFrom); atpa := self.findWithinATPA(enclosingObj, searchBox, sortFrom); tpa := atpa.merge(); tpa.sortFromPoint(sortFrom); result := tpa; {$IFDEF TOBJ_DEBUG} mbmp.debugTPA(tpa, true); DisplayDebugImgWindow(bmpW,bmpH); DrawBitmapDebugImg(bmp); FreeBitmap(bmp); {$ENDIF} end; // ----------------------------------------------------------------------------- // TObjFind.findWithinBoxes(enclosingObj: TObjFind; // searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]) // // Finds one object inside of another. Returns the found object in box form // ----------------------------------------------------------------------------- function TObjFind.findWithinBoxes(enclosingObj: TObjFind; searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TBoxArray; var tpa: TPointArray; atpa: T2DPointArray; i: integer; bmp, bmpW, bmpH: integer; mbmp: TMufasaBitmap; begin {$IFDEF TOBJ_DEBUG} writeln('In TObjFind.findWithinBoxes'); {$ENDIF} if (self.filterSpec.filterStyle = TObjFilterStyle.NONE) then begin writeln('ERROR: TObjFind.findWithinBoxes: Cannot find boxes since filterStyle=NONE'); setLength(result, 0); exit; end; self._validateBoxes(searchBox, sortFrom); atpa := self.findWithinATPA(enclosingObj, searchBox, sortFrom); {$IFDEF TOBJ_DEBUG} GetClientDimensions(bmpW,bmpH); bmp := BitmapFromClient(0, 0, bmpW-1, bmpH-1); mbmp := getmufasabitmap(bmp); {$ENDIF} // filtering should already be done, we're doing it twice? tpa := atpa.merge(); atpa := self._doFilterATPA(tpa, self.filterSpec, 'TObjFind.findWithinBoxes', 9999999); setLength(result, length(atpa)); if (length(atpa) = 0) then exit; for i := 0 to high(atpa) do atpa[i].sortFromPoint(sortFrom); atpa.sortFromFirstPoint(sortFrom); for i := 0 to high(atpa) do begin result[i] := atpa[i].getBounds(); {$IFDEF TOBJ_DEBUG} mbmp.drawBox(result[i]); {$ENDIF} end; self.lastFoundBox := result[0]; self.lastFoundPoint := result[0].getMiddle(); {$IFDEF TOBJ_DEBUG} DisplayDebugImgWindow(bmpW,bmpH); DrawBitmapDebugImg(bmp); FreeBitmap(bmp); {$ENDIF} end; // ----------------------------------------------------------------------------- // TObjFind.findWithinCenters(enclosingObj: TObjFind; // searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]) // // Finds one object inside of another. Returns the centers of each boxed // item found. // ----------------------------------------------------------------------------- function TObjFind.findWithinCenters(enclosingObj: TObjFind; searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TPointArray; var objBoxes: TBoxArray; i, len: integer; bmp, bmpW, bmpH: integer; mbmp: TMufasaBitmap; begin {$IFDEF TOBJ_DEBUG} writeln('In TObjFind.findWithinCenters'); GetClientDimensions(bmpW,bmpH); bmp := BitmapFromClient(0, 0, bmpW-1, bmpH-1); mbmp := getmufasabitmap(bmp); {$ENDIF} self._validateBoxes(searchBox, sortFrom); objBoxes := self.findWithinBoxes(enclosingObj, searchBox, sortFrom); len := length(objBoxes); setLength(result, len); if (len = 0) then exit; for i := 0 to len-1 do begin result[i] := objBoxes[i].getMiddle(); {$IFDEF TOBJ_DEBUG} mbmp.drawCross(result[i], 10, clRed); {$ENDIF} end; {$IFDEF TOBJ_DEBUG} DisplayDebugImgWindow(bmpW,bmpH); DrawBitmapDebugImg(bmp); FreeBitmap(bmp); {$ENDIF} end; // ----------------------------------------------------------------------------- // TObjFind.chooseWithin(enclosingObj: TObjFind; searchBox: TBox; // sortFrom: TPoint; matchUptext: TStringArray; excludeUptext: TStringArray; // mouseStyle: integer; choices: TStringArray; byBox: boolean = false; // pointVariance: integer = 5) // // Chooses an object within another object based on mousetext // ----------------------------------------------------------------------------- function TObjFind.chooseWithin(enclosingObj: TObjFind; searchBox: TBox; sortFrom: TPoint; matchUptext: TStringArray; excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray; byBox: boolean = false; pointVariance: integer = 5): boolean; begin end; ////////////////////////////////////////////////////////////////////////////////// ///// Terminator / Initializer ////////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // _objTerminate() // // Frees up anything we loaded // ----------------------------------------------------------------------------- procedure _objTerminate(); begin end; // ----------------------------------------------------------------------------- // INITIALIZER // // Hooks our terminator into the termiation process, checks for online updates // ----------------------------------------------------------------------------- begin addOnTerminate('_objTerminate'); updater.check4update('libobjfind.simba', libobjfindVersion, libobjfindVerUrl, libobjfindCodeUrl, true); end;