Macro Scripting for Dummies

Need some help with that macro you're working on or aren't quite sure how to get your macro to do something? Ask here!

Moderator: MacroQuest Developers

elretardo
a lesser mummy
a lesser mummy
Posts: 36
Joined: Thu Oct 16, 2003 3:24 am

Macro Scripting for Dummies

Post by elretardo » Wed Nov 12, 2003 8:19 am

Macro Scripting for Dummies by elretardo
----------------------------------------


INTRODUCTION

A macro is a user-defined automated task, simply put. They've been around forever. I remember
using them 9-10 years ago in my BBS days with the Procommm Plus terminal dialer. Somewhere along
the lines, the term was stupified, most likely by the Microsoft empire, and what was known as the
Macro became the Hotkey. Hotkeys in EverQuest are limited macros that can only do certain things.
What our beloved Developers have done for us was create a program called MacroQuest, in which we
can use to do things that Sony doesn't wish us to do, automated style.

We can use the functions in MacroQuest by making EverQuest hotkeys to call them for us, but what's
the fun in that? We can write scripts that utilize many functions at the exact same time for us.
Enter, the wonderful world of Macro Scripting.

It may sound intimidating at first but generally, basic scripting is easy and painless. Since
MacroQuest script is an interpreter language, we don't even have to compile them. It's easier
than BASIC. There are a few things you need to know, though.


THE BASIC STRUCTURE OF SCRIPTS

The basic structure of scripts are its routines. A routine is exactly what it sounds like.
It's a sequence of events. Think of yourself as a code conductor. You place all of the
functions in their places, you tell them what to do, and you sit back and enjoy the show. Each
routine is labeled as a SUB ROUTINE, including the Main one. That's not nearly as confusing
as it may seem at first. It's just a label, really. A sub routine, in the context of a symphony,
would be the next sequence of events that you, the code conductor, would direct.

Each script starts with a MAIN Routine. This is the routine where your first functions will
be placed in every script. It looks like this:

Code: Select all

Sub Main
  MAIN FUNCTION(S) GO HERE
/return
Now that you've defined the brain of your script, your first instrument if you will, you can have
various functions call (with /call) different sub routines, which can be named however you want
them to be named. Your added sub routines will look like this:

Code: Select all

Sub SomeSubRoutineName
  SEPERATE FUNCTION(S) GO HERE
/return
You have probably noticed the /return in the routines and are wondering what the hell they do. Well,
/return basically tells the script to return to the routine in which it was called from. All
sub routines will end with /return. It's an easier way of saying, "Ok, I'm done with this crap, I'll
move back to the original routine now."

There you have it. You now know the wonderfully simple structure of the script's outer shell. Now
we'll go into filling those routines in with useful stuff.


VARIABLES

Variables are also exactly what they sound like. There is one confusing part though; that being
that there are two kinds of variables. The variable you will most likely find the most useful
is the one that is unchangeable and set in the programming code of MacroQuest, which begin with a
dollar sign ($). The second type of variable is a user-defined variable, which begins with an at (@)
symbol.

A listing of MacroQuest variables can be found in the MacroQuest manual at http://macroquest2.com/?p=manual.

(If you're just starting out in scripting, you should never close the manual window because it really
is a life saver.)

An example you can use to test a MacroQuest variable is to go inside of EQ with MacroQuest open
and use /echo followed by some MacroQuest variables. Like this:

Code: Select all

/echo $target(name)
Now, the most important and often times annoying thing about user-defined variables, is that you
must first /DECLARE them to be variables so MacroQuest knows to look for them when you try to use
them. This is done in the MAIN routine. In most cases, and for the sake of simplicity, in this case,
we'll /declare them as global variables, which means any sub routine may use them. Here is a sample:

Code: Select all

Sub Main
  /declare MYVARIABLE global
  /declare MYVARIABLE2 global
  /declare MYVARIABLE3 global
/return
Now that our variables (MYVARIABLE) are declared, we can set values to them using the /varset
command. /varset allows us to define these variables any way we wish, and also flags them with
the ever-important at (@) symbol so that we may use them in our script. Also remember that when
you try to set a variable with multiple words, you'll want to enclose the phrase in quotation marks.
This way MacroQuest knows that the variable string is indeed a phrase of more than one word.
For example:

Code: Select all

Sub Main
  /declare MYVARIABLE global
  /declare MYVARIABLE2 global
  /declare MYVARIABLE3 global

[b]  /varset MYVARIABLE "This scripting stuff is easy as pie."
  /varset MYVARIABLE2 "Yep, it sure is."
  /varset MYVARIABLE3 123456[/b]
/return
You'll notice I threw a curveball at you and didn't put one of the strings in quotation marks. If
you're catching on, you'll know that's because it's a single word phrase. MacroQuest will take it
as it is.

Now that we've declared and set our variables, we can use other commands to call those variables
from the script. For the sake of of the retarded, such as myself, we'll make it as easy to
understand as possible and once again go back to our old friend, the /echo command. We'll
place the /echo in the script its self this time instead of using it manually from EverQuest, like
so:

Code: Select all

Sub Main
  /declare MYVARIABLE global
  /declare MYVARIABLE2 global
  /declare MYVARIABLE3 global

  /varset MYVARIABLE "This scripting stuff is easy as pie."
  /varset MYVARIABLE2 "Yep, it sure is."
  /varset MYVARIABLE3 123456

[b]  /echo @MYVARIABLE
  /echo @MYVARIABLE2
  /echo @MYVARIABLE3[/b]
/return
Put that in notepad and save it as testecho.mac in your macro directly, which is probably c:\mq\release\macros\

Once you've got it saved, go into EverQuest with MacroQuest open, and type /macro testecho.mac

You will see something that looks like this:

[MacroQuest] This scripting stuff is easy as pie.
[MacroQuest] Yep, it sure is.
[MacroQuest] 123456


With that, I congradulate you on your first simple script.


IF this tutorial sucks tell me or ELSE

Now that you've got your first simple script, we'll get into some more advanced stuff. All in all,
it's not really all that advanced but it can and will give you a headache at some point. That
headache is called the /if command. Again, it's exactly what it sounds like. The /if command
is the basic artificial intelligence of your script. IF something occurs, do something, basically.
Both the action and reaction are defined by you in your script. For example:

Code: Select all

  /if $target(name)==elretardo /echo This guy is retarded.
In plain english, that command is: If your target's name is elretardo, say to yourself that this
guy is retarded.


You probably noticed the == immediately. This is also not hard to understand. It means equals. So
/If your target equals elretardo blah blah, you get the picture. You can use the equals sign or some
of its counterparts like the greater than symbol, less than symbol, and so on. All of them can
be found in the manual, which you should still
have open.

Now, /IF your target is elretardo, you will go through the schpiel. However, because elretardo isn't
my actual EverQuest name, chances are that your target will be something different. If that is the
case, then our /IF needs to have a different action to take should the target be different. This is
called an ELSE.

An ELSE is usually defined by the next line in your routine automatically. /IF your target isn't
elretardo, MacroQuest will know to go to the next line and do its preprogrammed thing. Sometimes,
though this may be a viable option, you may need to define an ELSE that is exclusive to the /IF.

This is where we get into some more structure and are introduced to brackets ({}). Brackets are
used within sub routines to define a smaller sub routine. This is confusing, I know. But stick with
me. An opening bracket ({) will tell MacroQuest that a new routine is about to begin. Every opening
bracket naturally has a closing bracket (}) to be placed at the end of the routine. When using an ELSE,
you will have two sets of opening and closing brackets. One set for the /IF, and the other for the
ELSE.

I know you're confused as hell now so here's an example:

Code: Select all

Sub Main
  /if $target(name)==elretardo {
 	/echo This guy is retarded.
	/rude
     } else {
	/echo This guy is not retarded.
	/bow
     }
/return
What this is doing is asking /IF my target is elretardo, I will give him the finger and say to myself
This guy is retarded. Otherwise, I will bow to him and say to myself this guy is not retarded. It's
pretty simple once you get the hang of it, really.

I bet you're wondering if you can use variables in place of elretardo in the /IF statement. You can.
It is also as simple as it sounds. Let's say you want to use MYVARIABLE3 which we've defined as
123456. You would do it like this:

Code: Select all

Sub Main
  /declare MYVARIABLE global
  /declare MYVARIABLE2 global
  /declare MYVARIABLE3 global

  /varset MYVARIABLE "This scripting stuff is easy as pie."
  /varset MYVARIABLE2 "Yep, it sure is."
  /varset MYVARIABLE3 123456

  /if $target(name)==[b]@MYVARIABLE3[/b] {
 	/echo This guy is retarded.
	/rude
     } else {
	/echo This guy is not retarded.
	/bow
     }
/return
Chances are your target's name won't be 123456, either, since EverQuest doesn't allow you to have
numbers in your name. Prepare to do lots of bowing.

Not only can you use user-defined variables, you can use the MacroQuest variables as well. Since
I think you've got a grasp of it, I won't insult your intelligence and add another example.

Congradulations on your first script using an /IF and an ELSE.


Calling Sub Routines, Loops, and Commands commonly used in them

Now that we've got the basics of the Main routine down, I think it's time we venture into the much
chartered waters of the Sub routine. As stated before, sub routines are just routines that you
define to do a specific set of functions. They're most commonly used for loops. A loop is a set
of functions that you want to repeat. Let's say you want your script to keep checking for a spawn.
You'd need a loop to do that. To make a loop, you need to define a new sub routine. We'll name
it Sub Loop for the sheer dummy-friendly nature of it. Example:

Code: Select all

Sub Loop
/return
Now our loop is pretty damn useless without stuff we want to repeat, so let's introduce ourselves
to the /goto command, and its counterpart, the colon (:). No, not as in I get cancer, I kill jack.
As in the two little dots stacked on top of eachother. The colon (:) is used to define the beginning
of the loop. A bookmark, if you will. The very useful /goto command is used to tell the script
to begin the loop, or go to the bookmark. Example:

Code: Select all

Sub Main
  /call Loop
/return

Sub Loop
  /goto :loopstart

  :loopstart
  /target Stormfeather
  /goto :loopstart
/return
You'll notice that I've used the /goto command twice in the Loop sub routine. If you haven't figured
it out already, I'll explain. The first /goto command is issued to begin the loop and the second /goto
command is issued to sustain the loop. So the first says START THE LOOP, and goes to the :loopstart
bookmark. Then it attempts to target Stormfeather and issues another /goto command to retry the /target.

You'll also notice we've used the /call command for the first time. This one is also not cleverly
named. It's like calling someone on a phone but instead of their phone number, you enter the Sub Routine
you wish to call. No area code, no number. Just pure unadultered Sub Routine goodness. So, you call
the Loop sub routine and it immediately picks up the action and begins to loop the targeting process.

If you've already saved this script and used it, you'll know it causes a hell of a lot of spam. You can
easily twart this problem with a very useful command called /delay. Again, not hard to figure out
what it does. You can have the script delay it's next command in increments of either seconds (s) or minutes (m).
The manual says that without a S or a M to define whether you want seconds or minutes, MacroQuest will automatically
assume you mean 10ths of a second. Don't listen to it. It defaults to seconds.

So now that we want to kill the spam of the targeting process, I'll throw up another example:

Code: Select all

Sub Main
  /call Loop
/return

Sub Loop
  /goto :loopstart

  :loopstart
  /delay 1s
  /target Stormfeather
  /goto :loopstart
/return
Now that we've added /delay 1s, you'll notice your spam reduce a whole lot. One simple command
can save so many headaches. If only I had that in real life to use in conjunction with my tendancies
to procrastinate.

Code: Select all

Sub Main
  /delay 120m
  /say Yes, dear.
/return

Using what you've learned to make an adaptable spawn checker

Now you've got all the basic knowledge to make a simple spawn checker script but can you make it
adaptable using user-defined variables? I think so. If not, I'll do it for you because this tutorial
would be lame if I didn't add examples.

Here it is!

Code: Select all

Sub Main
  |**

  Spawn Checker v1.0 by [Your Name Here]
  It's Shake n Script and elretardo helped!

  **|

  | Declare and set Spawn Name to Stormfeather
  /declare SpawnName global
  /varset SpawnName Stormfeather

  | Call Sub CheckIt
  /call CheckIt
/return

Sub CheckIt
  | Start the first loop..
  /goto :isitup

  :isitup
  | Check if Stormfeather is up
  /if $target(name)==@SpawnName {
  	/echo @SpawnName is up!
	| Do a dance of joy since the bastard never spawns and you're the luckiest man/woman alive.
	/dance
	| End the spamfest.
	/endmacro
	} else {
	| Go to the actual spawn checking loop (Again?).
	/goto :checkloop
	}
  :checkloop
  /delay 1s
  /target @SpawnName
  | Let's go back and see if he's spawned yet..
  /goto :isitup
/return
Now you're thinking, what the hell is that crap at the top and how come there's 2 loops in there
under one sub routine!? It's because I'm a curveball throwin mofo, my friend. First off, the crap
at the top there that begins with |** and ends with **| is used to define a multi-line comment.
It's basically a bracket that MacroQuest completely ignores so you can add various comments throughout
your code to usually give yourself recognition or to point out certain pieces of code to yourself.
You can also use a pipe (|) if you only want to comment on a single line. Just think of them as
post-it notes.

A new command has also been introduced which is /endmacro. You can issue this command from a script
to end your macro automatically after the objective of the script is met or you can use it while you're
testing a script if something isn't right and you want to start it again after making some changes.

Two loops are used in the CheckIt sub routine. One is an /IF ELSE loop that will check to see if you've
got Stormfeather targeted. If you do, it will echo that he's up, do a dance of joy, and end the macro.
If you don't, the script will go to the second loop which is the loop we used in the previous example
to actually check the spawn.

We've also got a brand new variable in there which can be set at the will of the person who uses
the script. If you noticed, the /IF command issued uses it as well. This is how you make adapatable
scripts.

You've just completed my tutorial on Macro Scripting for Dummies. If you listened, you're on your
way to making scripts that you can post to the Macro Depot so myself and those
like me can rape your intellectual work to either make it better or make it annoyingly worse. If you
didn't listen, I guess you'll need to reread the tutorial and until you listen, just use us scripting
folk for our sweet sweet macro love.

Oh yeah -- If I haven't said it enough, RTFM.


Postscript Notes

From DekeFentle, FlashG, and Macrofiend--

You can use ~~ in place of == if you're unsure of the exact string in which you want to compare, or if the string changes within EverQuest. Example:

Code: Select all

  /if $target(name)~~"tardo" /echo I think it's that moron, elretardo.
Where our original script called for ==elretardo because we knew the exact name of the target, we could use ~~ to compare a string in which we don't know the exact name. The quotation marks tell the script that the enclosed string is the one we want to compare and if the string has spaces in it, it would keep the script from crashing.

Quotation marks should be used in any multi-word phrase, be it a /varset or an /if.

Also, if you're trying to compare a number, you'll want to use the n flag before /if to tell the script that you want to compare a number instead of a phrase. Example:

Code: Select all

  /if n $target(hp,pct)==100 /echo I just used a number in my /if statement.
The new variable string we used is $target(hp,cur) which will return the current hitpoint percent of your target. /If n our target's hitpoints are at 100% say to oursevles I just used a number in my /if statement. You'll want to use the n flag any time you want to compare a number in your /if statement.


From Mckorr (Developer)--

It is a good practice to end your main sub routine with /endmacro instead of /return. The reason for this is that we should tell our script that even though it's the first listed routine, it is also the last. Although /return does work in all sub routines, /endmacro is the proper way to end your Main sub routine from the standpoint of the experienced coder/scripter. Example:

Code: Select all

Sub Main
  /dance
  /bow
  /dance
  /echo Doing the Roboto dance..
/endmacro
Because we have a macro with only one sub routine and within that sub routine is only one set of commands, we will tell the macro to end its self after the objective of the script has been met. If you're using a macro script with loops and many routines, this isn't all that important. However, it is pretty important for a macro that has only one unlooped function.
Last edited by elretardo on Wed Nov 12, 2003 7:05 pm, edited 1 time in total.
I must go, for there they go and I am their leader.

DyvimTvar
orc pawn
orc pawn
Posts: 16
Joined: Wed Oct 22, 2003 1:21 pm

Damn well done

Post by DyvimTvar » Wed Nov 12, 2003 9:00 am

Elretardo,

Well done man. I must say that is very well written and should be stickied someplace where every one who is new to scripting will see right off. I think I knew most of this stuff and still found it a very enjoyable read and very helpful in simplifing the whole process.

I am sure it was a lot of work, but if you find it in your heart to write a chapter two with event triggers and calling a script with paramaters passed from the command it would be most cool.

Thanks again for a great intro tutorial.
DyvimTvar

User avatar
vzmule
Contributing Member
Contributing Member
Posts: 378
Joined: Thu Mar 13, 2003 11:56 pm

Post by vzmule » Wed Nov 12, 2003 9:02 am

Nice work.

DekeFentle
a lesser mummy
a lesser mummy
Posts: 48
Joined: Wed Oct 22, 2003 1:41 pm

Post by DekeFentle » Wed Nov 12, 2003 10:34 am

I agree, well done but I'm a bit confused (I live most of my life that way) shouldn't your example,

Code: Select all

/if $target(name)==elretardo /echo This guy is retarded. 
use the ~~ as the comparison oporater because you're comparing text? Does == work in this instance? I would crank it up and test but I'm at work and don't have access.
Revelation 6:8

MacroFiend
a grimling bloodguard
a grimling bloodguard
Posts: 662
Joined: Mon Jul 28, 2003 2:47 am

Post by MacroFiend » Wed Nov 12, 2003 11:35 am

The == would work as long as the text is an exact match. The ~~ would match if it existed in the string.

Another side note, if $target(name) had two or more words in it "an aborean sprout" for example, the script would crash because $target(name) wasn't wrapped in quotes.

Mckorr
Developer
Developer
Posts: 2326
Joined: Fri Oct 18, 2002 1:16 pm
Location: Texas

Post by Mckorr » Wed Nov 12, 2003 1:16 pm

One change I would make, for the sake of clarity. Sub Main should end with /endmacro, not /return. Yes I know, /return works, but using /endmacro delineates to the user that this is where the macro itself ends, and that we won't be returning to another function.
MQ2: Think of it as Evolution in action.

FlashG
Contributing Member
Contributing Member
Posts: 104
Joined: Thu Jul 11, 2002 6:38 pm

==

Post by FlashG » Wed Nov 12, 2003 1:59 pm

Great job.

When making comparisons you should stress 4 points. 1) Get into the habbit of using quotes to eliminate the problems with imbedded spaces. 2) Also using ~~ is a easy (and lazy) way to do comparisons, I use both == and ~~ depending on what I am looking for. 3) Use the /if n when comparing numbers. 4) Using negitive logic can be confusing to read but somtimes save a bunch of code.

flashg

in_training
Craptastic
Posts: 115
Joined: Fri Oct 10, 2003 8:25 am

Post by in_training » Wed Nov 12, 2003 5:45 pm

Please do one for events!

They piss me off. I can program, but events are stressing me out.
Subroutines
Every macro then has a Sub Main that defines the macro's entry point.
Additional Subs can be defined in the code and /called.
Special subroutines starting with Event_ are used by /doevents

Sub Event_Chat: Event callback for messages
$p0 - Channel of message (ooc etc., chat channels will callback with the name of the channel)
(ie. /join port ... callback will have $p0==port)
$p1 - Name of the person who sent the message
$p2 - Text they sent

Sub Event_Timer: Event callback for when a timer hits 0
$p0 - Timer that fired
$p1 - Value timer was originally set to

Sub Event_name: Custom event callback defined by #event
$p0 - Line that triggered event
Is that helpfile outdated?

wassup
Official Guardian and Writer of TFM
Official Guardian and Writer of TFM
Posts: 1487
Joined: Sat Oct 26, 2002 5:15 pm

Post by wassup » Wed Nov 12, 2003 6:21 pm

in_training wrote:Please do one for events!

They piss me off. I can program, but events are stressing me out.
Subroutines
Every macro then has a Sub Main that defines the macro's entry point.
Additional Subs can be defined in the code and /called.
Special subroutines starting with Event_ are used by /doevents

Sub Event_Chat: Event callback for messages
$p0 - Channel of message (ooc etc., chat channels will callback with the name of the channel)
(ie. /join port ... callback will have $p0==port)
$p1 - Name of the person who sent the message
$p2 - Text they sent

Sub Event_Timer: Event callback for when a timer hits 0
$p0 - Timer that fired
$p1 - Value timer was originally set to

Sub Event_name: Custom event callback defined by #event
$p0 - Line that triggered event
Is that helpfile outdated?
Look at the readme in the zip or cvs. The help file on the web site itself hasn't been changed yet.

elretardo
a lesser mummy
a lesser mummy
Posts: 36
Joined: Thu Oct 16, 2003 3:24 am

Post by elretardo » Wed Nov 12, 2003 7:10 pm

Edited and added in some useful postscript notes :)

Also, I couldn't write a chapter on events because I don't get them myself. I've not played with events much so I'm not the man for that job :)

Although, I will state: /doevents should be done away with and when an event is defined in a macro that loops its self, MacroQuest should automatically call the Sub Routine Event that is specified in its corresponding #event when the string occurs.

There's my 2¢ for making it more understandable and retard-friendly hehe.
I must go, for there they go and I am their leader.

User avatar
Rage
orc pawn
orc pawn
Posts: 17
Joined: Sat Oct 19, 2002 2:15 am
Location: San Diego, CA USA

Post by Rage » Fri Nov 14, 2003 8:49 pm

You forgot "Hello, world!" :(

wassup
Official Guardian and Writer of TFM
Official Guardian and Writer of TFM
Posts: 1487
Joined: Sat Oct 26, 2002 5:15 pm

Post by wassup » Fri Nov 14, 2003 10:49 pm

Here is a short example of events:

Code: Select all

|Useage: /macro testmacro "mastername"
#chat tell
#event SoWFaded "your spirit of wolf has worn off"
#event FoSoFaded "your focus of soul has worn off
Sub Main
   /declare Master global
   /varset Master @Param0
   :Loop

   |Checks for #event occuring
   /doevents

   |clears the event queue
   /doevents flush

   /delay 10s
   /goto :Loop
/endmacro

|This event occurs when SoW fades from you
Sub Event_SoWFaded
   /target myself
   /cast item "black fur boots"
/return

|This event occurs when FoSo fades from you
Sub Event_FoSoFaded
   /target myself
   /cast "focus of soul"
   /delay 60
/return

Sub Event_Chat
   |All tells are processed by /doevents. Code here will execute based on your code
   |channel watched is defined by #chat tell
   /if @Param1=="@Master" {
      >code to execute>
   }
/return

elretardo
a lesser mummy
a lesser mummy
Posts: 36
Joined: Thu Oct 16, 2003 3:24 am

Post by elretardo » Sat Nov 15, 2003 6:25 am

I understand having a /doevents loop but my issue is that the majority of the scripts I use have multiple loops and sometimes /doevents won't work correctly if there are functions laid out before it. The process is simple if there's only one loop in the script but in a script with multiple loops, I turn into a true retard and my brain goes to mush. Hence my idea of MacroQuest checking for #event occurances automatically and running their corresponding event sub routine.

So let's say I put #event BeingHit " points of damage!" at the top of my script. What if instead of user-driven /doevents placed strategically throughout the script, we have MacroQuest create loops for each event outside of the script its self and upon one of them occuring, the corresponding event sub routine is run..

Sub Event_BeingHit
/doability "Feign Death"
/return

Wish I could code hehe..
I must go, for there they go and I am their leader.

Midnight
a lesser mummy
a lesser mummy
Posts: 68
Joined: Wed Nov 12, 2003 12:51 pm

Post by Midnight » Sat Nov 15, 2003 10:00 am

Theoretically that sounds good, but you run into problems calling the events outside of the macro (per say in an .ini file listing all Events you want called through a number of scripts.. or an 'always running' event macro). If you use code with no variables, this can be implemented easily. But since most events comprise of variables being changed, you can't pass the correct values through different macros. I suppose you could dump the vars into an ini file and / or code something into MQ that would allow a macro over top of another one another. I'm sure it can be done though. For now, adding /doevents in the right spots doesn't seem all that daunting.

Yalp
a ghoul
a ghoul
Posts: 90
Joined: Thu Dec 05, 2002 6:28 pm

Post by Yalp » Tue Dec 02, 2003 1:46 pm

Great post el ---

manual link no longer works, it can be found here

http://macroquest2.com/main.php?p=manual
Because i wouldn't have it any other way