PDA

View Full Version : AeroLib - Core functionality



Flight
07-05-2015, 02:29 AM
AeroLibrary - Core functionality

Welcome to the introduction to basic types & routines of AeroLib. While getting to know this library and writing scripts may be much different than you're used to, chances are you'll find it much easier to use as opposed to the pascal version of SRL-OSR. So let's jump into it and get acquainted with AL.



TColEx
[AeroLib > core > engine > Color.simba]

TColEx is at the very core of AeroLib, the life blood if you will. This little guy is a package containing a color, color tolerance and the hue & saturation modifiers, which are optional. This type completely replaces normal color-finding routines that you're used to. The basic idea is from Janilabo's TColorSetting (https://villavu.com/forum/showthread.php?t=101777) work, with the same concept in mind but simplified, automated and Lape-styled.



Type TColEx = record
Col : Integer;
Tol : Integer;
hMod : Extended;
sMod : Extended;
end;


Col : The main color you'll be searching for.
Tol : The tolerance of the color.
hMod: Hue modifier of a CTS2 (Color tolerance speed 2) based color. (Optional)
sMod: Saturation modifier of a CTS2 (Color tolerance speed 2) based color. (Optional)


procedure TColEx.create(_Col, _Tol: Integer; _hMod, _sMod: Extended);
procedure TColEx.create(_Col, _Tol: Integer); overload;
function createCol(_Col, _Tol: Integer; _hMod, _sMod: Extended): TColEx;
function createCol(_Col, _Tol: Integer): TColEx; overload;

These four functions will accomplish the same thing: create & store a TColEx that you can use later. While the first two will be used like this:

// Define your TColEx
color_White : TColEx;

color_White.create(15660260, 10, 1.26, 0.03); // Automatically becomes a CTS2-based color
or
color_White.create(15660260, 10);

Whereas the other two functions, while accomplishing the same, are used slightly different:

// Define your TColEx
color_White : TColEx;

color_White := createCol(15660260, 10, 1.26, 0.03); // Automatically becomes a CTS2-based color
or
color_White := createCol(15660260, 10);

Now your color is created & stored and ready to be used.

function TColEx.findIn(Area: TBox; var Point: TPoint): Boolean;
function TColEx.findIn(Area: TBox): Boolean; overload;

These two functions will tell you if a TColEx is found in an area, defined by a TBox. A helpful hint to keep in mind here is that AeroLib has a few predefined areas, such as:

AREA_MS (your mainscreen)
AREA_MM (your minimap)
AREA_INV (your inventory/all game tabs)

The first function will tell you if the TColEx is found in an area as well as give you the first point at which it's found. The second function will only tell you if it's found in the area or not.

procedure ColorTest();
var
color_White : TColEx;
foundPnt : TPoint;
begin
color_White.create(15660260, 10, 1.26, 0.03);
if color_White.findIn(AREA_MM, foundPnt) then
writeln('Found white on the minimap at '+toStr(foundPnt));
end;

You'll noticed I used a TColEx that contains a hMod/sMod. Whenever a TColEx containing a hMod and/or sMod is searched for, Simba's CTS is automatically temporarily switched to 2 and when the function has finished its search, the CTS will be returned to whatever it was before hand; you don't have to deal with the switching yourself, or even worry about colors not being found because the CTS was incorrect.

function TColEx.findAllIn(Area: TBox; var Points: TPointArray): Boolean;
function TColEx.count(Area: TBox): Integer;
function TColEx.findPie(Area: TBox; StartD, EndD, MinR, MaxR: Extended; MidX, MidY: Integer; var Points: TPointArray): Boolean;

TColEx.findAllIn() does the same as above but instead of returning a single point at which the color is found, you'll receive an array of points (TPA) where all of that TColEx is found in your 'Area'.
TColEx.count simply counts the number of pixels of that TColEx found in the 'Area' and results in that total count.
TColEx.findPie finds all the TColEx pixels in a circle. Useful for precise color-finding in areas such as your minimap orbs.

procedure TColEx.autoCreate(colors: TIntegerArray);

This little procedure will create (& store) a single CTS2-based TColEx for you by merging the colors you provide it. It's based off of ProphesyOfWolf's neat little snippet which I haven't been able to find again. It works the same way as if you were using AutoColor, so keep your array of colors close in tolerance!
function TColEx.waitFindIn(Area: TBox; waitPerLoop, maxWait: Integer): Boolean;
The same thing as 'waitFindColorIn' but for TColEx. Useful for optimizing scripts or in situations where speed is critical.



TMSObjects
[AeroLib > entities > object > Object.simba]

TMSObjects are MainScreen objects that can be used throughout your script. This simplifies finding objects such as trees, bank booths, NPCs, players and so on. Be warned though, understanding how TMSObjects are detected may be complex for some.


type TMSObject = record
Name : String;
UpText : TStringArray;
InnerCols : Array of TColEx;
OuterCols : Array of TColEx;
MinCount : Integer;
Height : Integer;
Width : Integer;
SizeTol : Integer;
end;


Name: The name you assign to your TMSObject (not important)
UpText: an array of strings to search for when we're looking for the object
InnerCols: Array of TColEx to search for (most unique colors of the Obj)
OuterCols: Array of TColEx located near with the InnerCols
MinCount: Minimum color count (Inner color)
Height: Size of object (Y axis)
Width: Size of object (X axis)
SizeTol: Size tolerance for both height and width


procedure TMSObject.create(_Name: string; _UpText: TStringArray; _InnerCols, _OuterCols: Array of TColEx; _MinCount, _Height, _Width, _SizeTol: Integer);
procedure TMSObject.create(_Name: string; _UpText: TStringArray; _InnerCols: Array of TColEx; _MinCount, _Height, _Width, _SizeTol: Integer); overload;
procedure TMSObject.create(_Name: string; _UpText: TStringArray; _InnerCols: Array of TColEx; _Height, _Width, _SizeTol: Integer); overload;
procedure TMSObject.create(_Name: string; _UpText: TStringArray; _InnerCols, _OuterCols: Array of TColEx); overload;
procedure TMSObject.create(_Name: string; _UpText: TStringArray; _InnerCols: Array of TColEx); overload;

All of these procedures create your TMSObject, however how accurate you want your object to be found is determined by which of these procedures you use to create it. So let's take a look at how the searching is done so we can better understand how to create an accurate TMSObject.

function TMSObject.findAll(DistMod: Integer; SortFrom: TPoint; var Pnts: TPointArray): Boolean;

Ok so this function is the basic finder for a TMSObject and all the others will call this one. DistMod is how near at least one pixel of your InnerCol & OuterCol must be from each other to be a possible match. This is only taken into account if you have at least 1 'OuterCol', if not this parameter is not important. SortFrom is pretty straight forward as being the point at which all of your possible TMSObjects are sorted from. Generally this would be Point(MSCX,MSCY) (also predefined in AL as "MSCP") as in most cases we'd want to find the TMSObject that's nearest to our player, assumed to be the middle of the main screen. Pnts is an array of points containing the middle point of every possible TMSObject found.

Since it's possible to search for TMSObjects using dual-colors, AL utilizes bg5's AND_TPA plugin (https://villavu.com/forum/showthread.php?t=83795). Confused yet? Let's see a visual example of what's going on.

http://puu.sh/yjde
^ This is the plugin at work, searching for 2 sets of colors that appear within 10 distance of each other. The silver color is marked as white while the wooden color is marked as green, and the wooden color in which is found near (10 distance or less) with a silver color is marked as red.
To create a TMSObject to be found this way could be as simple as this:

procedure funWithObjs();
var
obj_Chest : TMSObject;
Pnts : TPointArray;
begin // Wooden color // Silver color
obj_Chest.create('Chest', ['Open Bank chest','Bank chest'], [createCol(7373972, 5, 0.10, 0.96)], [createCol(3956855, 5, 0.19, 1.07)]);
if obj_Chest.findAll(10, MSCP, Pnts) then
writeln('Found a possible '+toStr(length(Pnts))+ 'chests')
else
writeln('Failed to find the chest!');
end;

However calling 'TMSObject.findAll()' will only give you a TPointArray of the middle of every possible result, it does not handle mouse-moving & UpText-checking; this is done in the next function we'll cover but before that let's see how much more accurate AeroLib can be in finding these objects.
While the above is an example of using just an 'InnerCol' as well as an 'OuterCol', we could also create an TMSObject with only an 'InnerCol', which is the equivalent of the most advanced object-finding routines in SRL-OSR. But we can go much further than single and dual-color objects, let's look at size and count filtering.

http://i.imgur.com/gz0v0Ec.png

Let's say we want to find this object using all available information: Inner/OuterCols, MinCount, Height/Width, SizeTol. We would declare this TMSObject like so:

obj_Chest.create('Chest', ['Open Bank chest','Bank chest'], [createCol(7373972, 5, 0.10, 0.96)], [createCol(3956855, 5, 0.19, 1.07)], 300, 50, 70, 20);

The last 4 parameters are the additional ones we added: MinCount, Height, Width and SizeTol respectively. Now to successfully find the TMSObject the possible objects must contain more than just those 2 colors but also must have a minimal of 300 in that 'InnerCol' that was found near with the 'OuterCol'. The bounds of that 'InnerCol' must be 50x70 (height/width) with a 20 tolerance to both. For each possible object found that meets all of these requirements a point of the middle of the 'InnerCol' TPA is added to the "Pnts" variable, which we'll receive from the function upon successfully finding at least 1 TMSOjbect.

Hopefully I haven't lost you and you're still up to speed with my explanations. That version of a TMSObject is the most complex, and if you've looked through all of the possible procedures to create a TMSObject you'll see simplified alternatives. Here's some examples:

// TMSObject containing only an 'InnerCol'
obj_Chest.create('Chest', ['Open Bank chest','Bank chest'], [createCol(7373972, 5, 0.10, 0.96)]);

// TMSObject containing both an 'InnerCol' and 'OuterCol'
obj_Chest.create('Chest', ['Open Bank chest','Bank chest'], [createCol(7373972, 5, 0.10, 0.96)], [createCol(3956855, 5, 0.19, 1.07)]);

// TMSObject containing only an 'InnerCol' but also height/width/sizetol
obj_Chest.create('Chest', ['Open Bank chest','Bank chest'], [createCol(7373972, 5, 0.10, 0.96)], 50, 70, 20);

// TMSObject containing only an 'InnerCol' but also mincount/height/width/sizetol
obj_Chest.create('Chest', ['Open Bank chest','Bank chest'], [createCol(7373972, 5, 0.10, 0.96)], 300, 50, 70, 20);

// TMSObject containing an 'Inner/Outer Col', mincount/height/width/sizetol
obj_Chest.create('Chest', ['Open Bank chest','Bank chest'], [createCol(7373972, 5, 0.10, 0.96)], [createCol(3956855, 5, 0.19, 1.07)], 300, 50, 70, 20);

So you see the accuracy of finding a TMSObject is determined by how much information about it you provide when creating the TMSObject as opposed to finding it; that part is all handled automatically based on the information stored in the TMSObject.

Ok that's by far the most complex part of this little tutorial, so if you've made it this far and keeping up then it's smooth-sailing from here on out. Next we'll take a look at the remaining functions.

function TMSObject.find(DistMod: Integer; SortFrom: TPoint; var Pnt: TPoint): Boolean;
function TMSObject.find(var Pnt: TPoint): Boolean; overload;
function TMSObject.find(): Boolean; overload;

These functions will actually take into account the UpText you provided when you created your TMSObject. When one of these functions are called then, if one or more are found, your mouse will move to each of the TMSObjects found and check the UpText for a match. The first function has 3 parameters, 'DistMod' is the distance in which the 2 colors must come to each other. So if you're looking for a rock with a silver vein in it, you'd want the rock color as your 'InnerCol' and the silver vein color as your 'OuterCol' and your 'DistMod' should be quite small (I recommend around 10, which is default). 'SortFrom' is the point in which to start searching from. And last is 'Pnt', the first point at which the TMSObject is found, including the UpText match. The result will be True if the TMSObject was successfully found.
The second function works the same but has 'DistMod' set to 10 as default and 'SortFrom' is set to MSCP (center of the main screen). It will also return the point at which the TMSObject is found, if at all. The third function only results True if found.

function findMSObjectSimple(Cols: TIntegerArray; UpTextArray: TStringArray): Boolean;

This is a good function for beginners as it will do your object-creating & finding for you, all you have to provide is the color(s) and the uptext of the object you want to find. Do you remember the TColEx.autoCreate() function? That's used here, so remember, if you use multiple colors make sure they're of the same basic color for the auto-color to work accurately.



TItems
[AeroLib > entities > items > Items.simba]

A TItem is a package to store information for an item so you can easily use it throughout your script. It's useful for finding specific items in your inventory, your bank, deposit screens, shop screens, trade screens and even the Grand Exchange screen. This branch of the library comes with many useful functions to save time and space in your script.



type TItem = record
Name : string;
DTM : integer;
BMP : integer;
BMP_TOL : integer;
Wieldable : boolean;
end;


Name: A name assigned to your packaged item
DTM: A stored DTM of the item, used for locating
BMP: A stored bitmap of the item, used for locating
BMP_TOL: The tolerance of the TItem's BMP (only if using a BMP)

Defining a TItem is very easy and there's multiple ways to do it. Here's a couple examples:

procedure itemMadness();
var
item_Coal : TItem;
begin
item_Coal.Name := 'Coal';
item_Coal.DTM := DTMFromString('mggAAAHicY2NgYJjGxMDQAcR9TBD2QiCeBc QcjAwMbEDMD8RiQMwDxCxArKcnD9TFiIG5sIrixhAAAOC3BG4= ');

// OR
with item_Coal do
DTM := DTMFromString('mggAAAHicY2NgYJjGxMDQAcR9TBD2QiCeBc QcjAwMbEDMD8RiQMwDxCxArKcnD9TFiIG5sIrixhAAAOC3BG4= ');

// OR
with item_Coal do
begin
Name := 'Coal';
BMP := BitmapFromString(WIDTH, HEIGHT, 'YOUR BITMAP STRING GOES HERE');
end;
end;

Now that our TItem is created we can utilize it in our script, but there's one important rule you must always keep in mind when using TItems: Because they're composed of a DTM or Bitmap you must always free the DTM/Bitmap after it's no longer used. A golden rule for using any DTM and/or Bitmap in general is to declare it globally one time at the beginning of your script and free it before the script terminates. This will prevent you from have any memory leaks. Here's an example of correctly defining & freeing TIems:

program CoalFinder;
{$i AeroLib/AeroLib.Simba}

Var
item_Coal : Titem;

procedure searchForCoal();
begin
if item_Coal.inInventory() then
writeln('Found coal in the inventory')
else
writeln('Did not find coal');
end;

begin
initAL();

with item_Coal do
DTM := DTMFromString('mggAAAHicY2NgYJjGxMDQAcR9TBD2QiCeBc QcjAwMbEDMD8RiQMwDxCxArKcnD9TFiIG5sIrixhAAAOC3BG4= ');

searchForCoal();
// other stuff
// script loops
// more stuff

freeDTM(item_Coal.DTM);
end.

...and an example of how not to do it:

program CoalFinder;
{$i AeroLib/AeroLib.Simba}

procedure searchForCoal();
Var
item_Coal : Titem;
begin
with item_Coal do
DTM := DTMFromString('mggAAAHicY2NgYJjGxMDQAcR9TBD2QiCeBc QcjAwMbEDMD8RiQMwDxCxArKcnD9TFiIG5sIrixhAAAOC3BG4= ');
if item_Coal.inInventory() then
writeln('Found coal in the inventory')
else
writeln('Did not find coal');

freeDTM(item_Coal.DTM);
end;

begin
initAL();

searchForCoal();
// other stuff
// script loops
// more stuff
end.


Moving on we'll come to the TItem finding routines.

function TItem.findIn(Area: TBox; var fPnt: TPoint): Boolean;

Pretty straight forward, if the TItem is found in the specified area (TBox) then the result will be true, along with the point (fPnt) at which the item first appears.

function TItem.getAmount(ExcludeBank: Boolean): Integer;
function TItem.getAmount(): Integer; overload;

This neat little function will return the amount of the TItem, if it was actually found. If you set 'ExcludeBank' to true then the function will only count the TItem in your inventory. If 'ExcludeBank' is set to false (which is what the second function does) then, if found in the bank, the TItem will be counted (stack amount). Also something to keep in mind is if an item is "stackable", such as coins and bank notes, that will be automatically detected and the function will return the stack amount. However if the item is not stackable then the TItem will be counted for each slot it appears in within the inventory.

function TItem.getSlots(): TIntegerArray;
function TItem.getSlot(): Integer;

This is simply used to get all of the inventory slots in which the TItem is found, and the second function returns just the first slot to hold the TItem.

function TItem.interact(action: Variant): Boolean;

A very useful function combining many into one. This is to interact with a TItem, if found, in any action you want: left-click, hover or right-click choose option. Here's some usage examples:

procedure funWithCoal();
begin
if item_Coal.interact(MOUSE_LEFT) then
writeln('Clicked the coal')
else
writeln('Did not find coal');

if item_Coal.interact('Drop') then
writeln('Dropped the coal')
else
writeln('Did not find coal');

if item_Coal.interact(MOUSE_MOVE) then
if isUpText('Coal') then
writeln('Found coal uptext');
end;



And there you have it folks, a few of the most basic types found in the AeroLibrary. While this tutorial only scratches the surface of what all is available I truly hope it helps you to better understand the include, how it functions and how to take advantage of what it has to offer. If I have the time and willingness I'll write some more tutorials on second-level functionality in the include such as banking, walking and object-interacting. Perhaps even a step-by-step script creation tutorial to get you on your way to scripting.

ineedbot
07-05-2015, 02:46 AM
Hey great work on the include and tutorial, I indeed learned a thing or two. I really hope everyone switches over from pascalscript (srl-osr).

captainblast
07-14-2015, 09:02 PM
Going to try my hand at learning AeroLib :] Thanks for writing this up! Really hope I can learn it fast, tired of not being able to write my own scripts and having to rely on other people >_<

Appreciate the hard work you do, thanks Flight :D

Sh4d0wf0x
07-20-2015, 12:56 AM
Using this guide atm to create my first scripts ;)

anth_
07-21-2015, 10:43 AM
Useful stuff. Thanks Flight :D

Dan the man
07-21-2015, 01:53 PM
Silly question: can you use Aerolib with regular OSR-SRL?

If so, what order would you launch them in and how would you setup the main loop?

Kyle
07-21-2015, 02:00 PM
Silly question: can you use Aerolib with regular OSR-SRL?

If so, what order would you launch them in and how would you setup the main loop?

No, SRL-OSR is an outdated PascalScript include while Aerolib uses the newer Lape interpreter..

Dan the man
07-21-2015, 11:37 PM
No, SRL-OSR is an outdated PascalScript include while Aerolib uses the newer Lape interpreter..

Thank you kindly. I think i will have to look into this as a new route of scripting. Does it have the same functionality as the SRL-OSR include?
Had a look at the core files and while there were a few, weren't as many as the SRL.

Flight
07-22-2015, 12:58 AM
Thank you kindly. I think i will have to look into this as a new route of scripting. Does it have the same functionality as the SRL-OSR include?
Had a look at the core files and while there were a few, weren't as many as the SRL.

If there's something you'd like to see added to AeroLib you're more than welcome to suggest it/them on the official thread (https://villavu.com/forum/showthread.php?t=108953).

Dan the man
07-22-2015, 01:07 AM
If there's something you'd like to see added to AeroLib you're more than welcome to suggest it/them on the official thread (https://villavu.com/forum/showthread.php?t=108953).

Will do. Thanks :)

BillSimmons
07-29-2016, 11:49 PM
Just got Simba working. Going to try my hand at beginner stuff, wish me luck!

Also, I used RSbot back in the good ole days as a kid and it got me interested in programming. I went to state college but dropped out shortly after basics were completed and never got to my c++ major :/

This seems to be the first community worth joining after trying to find one for so many years (I'm picky, so that's a compliment to you guys.)

I know you will believe it when you see it but I seriously look forward to becoming a member of this community.

Athylus
08-28-2016, 09:05 PM
Hey Bill,

If you need some help we can look at the code together. I am also a beginner but I have pumped out a good amount of scripts the past years. Shoot me a PM if you are interested.

Villavu is definitely the best botting community out there. Everyone is very generous and willing to help if you put in the work yourself.

And thanks a lot Flight, the include is great. Without this my color scripts would never work.

Alan_K
08-30-2016, 07:26 PM
Hi guys,

I'm completely new to Simba and AeroLib and pretty overwhelmed at the moment. I can't seem to find any up to date and noob-friendly tutorials for AeroLib either - Can anyone point me to the correct resource? Really interested in learning this.

Dan the man
08-31-2016, 11:52 PM
Hi guys,

I'm completely new to Simba and AeroLib and pretty overwhelmed at the moment. I can't seem to find any up to date and noob-friendly tutorials for AeroLib either - Can anyone point me to the correct resource? Really interested in learning this.

These tutorials really helped me learn how to script in pascal, which I then transitioned to lape.

https://villavu.com/forum/showthread.php?t=44942
^This is outdated, but it is a step by step guide on using SRL to cut down your first tree. Will help you learn script layouts, procedures, color identification, variables, using functions etc.

https://villavu.com/forum/showthread.php?t=113715
^This gives you some more insight into Aerolib (Lape) so is a good resource once you have the basics nailed.

https://villavu.com/forum/showthread.php?t=58935
^THis is for bare beginners as it explains the different types like string (text), integer (whole numbers), boolean (true/false), procedures and functions etc.

We need to write up a few noob friendly Aerolib/Lape reflection tutorials because it can be a little off putting for an absolute novice, but if you have the burning desire to learn, then you have come to the right community, because everyone here is really helpful (if you show that you are trying).

Feel free to message me if you have any questions but I would recommend posting any code related questions here: https://villavu.com/forum/forumdisplay.php?f=491 so the whole community can give you a helping hand.

Hope to see you around :)

deepthroat123
12-17-2016, 12:09 PM
We need to write up a few noob friendly Aerolib/Lape reflection tutorials because it can be a little off putting for an absolute novice, but if you have the burning desire to learn, then you have come to the right community, because everyone here is really helpful (if you show that you are trying).

Please do! A lot of the guides on Aerolib are a bit old.

mellower
02-19-2017, 07:31 PM
Any possibilities to drop a couple lines about using rswalker, which is included on aerolib? It seems to be rather commonly used (core?) feature and I'm afraid that I'm unable figure my problem through reading the master.simba file.

Flight
02-21-2017, 02:57 AM
Any possibilities to drop a couple lines about using rswalker, which is included on aerolib? It seems to be rather commonly used (core?) feature and I'm afraid that I'm unable figure my problem through reading the master.simba file.

Yes, that's certainly something I should write a quick tutorial regarding, or cite it in this one, as AeroLib's way of utilizing RSWalker may differ from the original, stand-alone system. If I've enough time after releasing AL 2 then I'll try to whip something up to guide users in working with RSWalker in AL. If you're wondering why wait until the release? it's because the RSW system has changed quite a bit compared to previous revisions.

By the way, why master.simba? That's the file regarding random events. In your case you'll want to visit AeroLib/Core/minimap/Walker/Walker.simba.

EZ41
03-31-2017, 04:30 AM
hey,

I'm trying to paint the bounds of a TMSObject that I find for debugging purposes, similar to the images of the chest that Flight; has in the tut. I havent been able to get it working, I need to get the full bounds of the object, not just the center point(s). help would be appreciated. thanks

Dan the man
03-31-2017, 02:57 PM
hey,

I'm trying to paint the bounds of a TMSObject that I find for debugging purposes, similar to the images of the chest that Flight; has in the tut. I havent been able to get it working, I need to get the full bounds of the object, not just the center point(s). help would be appreciated. thanks

I would imagine something like this would work:

program new;
{$i AeroLib/AeroLib.Simba}

var
TinRock: TMSObject;
TPA: TPointArray;

begin
initAL;
TinRock.create('Mine Rocks', ['ine rocks', 'Mine', 'rock', 'ine r'], [createCol(5263705, 14, 0.14, 0.07)], 50, 0, 0, 0);
if TinRock.findAll(20, Point(MSCX, MSCY), TPA) then
DebugTPA(TPA, '');

end.

DebugTPA will create a little window in Simba and show a highlight of colors found in the TPA. Can also be done with ATPA.

MariusK
08-22-2017, 05:17 PM
Do TItems functions only work in SMART? I tried inInventory() on official OSRS client and it kept returning false. (No, I did not forget to select target) Same code works in SMART tho.

Dan the man
08-22-2017, 11:05 PM
Do TItems functions only work in SMART? I tried inInventory() on official OSRS client and it kept returning false. (No, I did not forget to select target) Same code works in SMART tho.

They work outside of Smart.

Paste your code and I will look at it for you if you want.

MariusK
08-23-2017, 08:28 AM
They work outside of Smart.

Paste your code and I will look at it for you if you want.

I tested it on another machine and it works perfectly, so the problem must be on my end. I'll just reinstall OS. Thanks.

Tog
12-21-2017, 01:08 AM
this is great! thanks for the tutorial.