Macro writing - quick tutorial

PeteSampras

Your UI is fucking you. Stop using it.
Joined
Dec 12, 2007
Messages
3,956
Reaction score
49
Points
38
Background: I get asked to write or mod macros quite a bit. As people start learning it themselves, they start really enjoying the process. I was asked to write a fast and concise wizard mac that uses hardcoded spells/AAs. It seemed easier to write it from scratch than to mod his existing one. So while I was writing it, I decided to write a how-to guide for this person and anyone else that may find it of benefit.

First go read: Getting Started - Macro Creation - MMOBugs Wiki

Ok, so you read that. Figure out something you want to do. It can be something easy, or something super complicated. The goal is to do it 1 piece at a time in manageable chunks. Start by writing down the skills you want to use.

A few considerations:
-You can use an INI or hardcode items. Hardcoding loads/executes faster but is less flexible and harder to modify.
-You can be very elaborate or do minimal checks
-Every line checks at the same relative speed. Checking 20 variables in 1 line is 20x faster than checking 1 variable in 20 lines.
-Be clear and concise.

Special stuff usually found at top of macro:
#turbo - how fast a macro cycles. base compile cap is #turbo 80, mmobugs is 500.
#define This That - replaces a phrase (This) with another phrase (That) whenever it appears in the macro
#include spell_routines.inc - loads another file to supplment the macro
#event EventName "#1# tells the group, '#2#'#*# - whenever this chat line is seen in game, it triggers an event routine that will be called Sub Event_EventName
Subroutines:
This is how anything gets done. You can have only 1 subroutine to unlimited subroutines to perform functions. All macros MUST have a Sub Main. Sub Main is automatically accessed when the macro starts, all other subroutines must use the /call command. Ie. /Call Nuke
Subroutines can receive variables and create new ones via using parentheses and you can pass them via the /call command. ie. /call Cast "Spell Name" gem5
Sub Cast(string spell,string gem) - That would receive the "Spell Name" as the new variable ${spell} and "gem5" as the new variable $gem}
Note that quoatation marks are used so that spaces are not an issue.
#chat eqbc - recognizes this as a chat to monitor
Plugins/TLO/members:
This is a comprehensive list of available TLO/Members for base compile: http://www.macroquest2.com/wiki/index.php/Data_Types
Plugins create Top Level Objects for you to use. Refer to wiki and datatypes to see what you can use. This is how you will access the information you want to use.
These are your ${Me.PctHPs}, ${Spell[Complete Heal].ID}, ${Target.CleanName}, ${Me.Class} type of things that will be required for you to interact with the game.
Variables:
There are 5 commonly used types of variables (there are more, just ive never seen anyone actually use them):
Code:
int - whole numbers
float - decimal point numbers
timer - timers that count down
bool - TRUE/FALSE
string - phrase or word or sequence of characters.  can ghetto function as an int
There are 3 variable scopes, or levels of existance.
Code:
local - exists only in the subroutine
outer - exists while the macro is running
global - exists until you shut down mq2/eq
To create variables you must /declare a type and a scope and value (default values will always be 0 or FALSE)
Code:
/declare Nuke1 string outer Flames of War
/declare Timer1 timer global 30s
/declare HitPoints int outer 95
/declare i int local
Looping:
If you want a macro to repeat its sub routines, you need to use loops. There are several types of loops that always need to be paired. Loops are local to the subroutine.

Example for/next loop:
/declare i int local
/for i 1 to 5
/echo Nuke${i}
/next i

That will declare i as a local integer variable. It will then cycle i as if its value was 1, 2, 3, 4, 5 and then continue on. For/Next loops can be cycled up or down, and can use different intervals if needed.

Similar example, but this time going from 20 to 2, with a 2 interval, ie. 20,18,16,...,6,4,2
/declare i int local
/for i 20 downto 2 step 2
/echo Nuke${i}
/next i

/goto loops:
:loop
stuff
/goto :loop

Do/while loops:
MQ2Loops - MMOBugs Wiki
Mastering those basics will allow you to complete anything you can imagine as long as it is possible.


Getting started:
So let's get started by figuring out what I want to do and grouping them into natural groups.

I want to make a wizard macro that will do the following things in this generic grouping/order:

Assist

Combat {
Aggro
Twincast
GoM
Nuke
AANuke
}

Rest {
Med
Harvest
SelfBuff
ModRod
}

Step 1. I feel like using spell_routines.inc to handle my casting, and I want to use the max default turbo setting. Let's start with that blank shell.
Code:
#turbo 80
#include spell_routines.inc

Sub Main(string assist,int amount)
:mainloop


/goto :mainloop
/return
Step 2. Add some variables. I prefer modular macros that separate activities in logical groupings so that it is easier for me to remember how to edit them. Others create macros in a single subroutine that does everything. Do whatever works best for you. To add my variables, I will create an Initalize routine.
Code:
Sub Initialize
|generic stuff
/noparse /declare GoM string outer (${Me.Song[Gracious Mana].ID}||${Me.Song[Gift of Mana].ID}||${Me.Song[Gift of Radiant Mana].ID}||${Me.Song[Gift of Dreamlike Exquisite Radiant Mana].ID}||${Me.Song[Gift of Exquisite Radiant Mana].ID}||${Me.Song[Gift of Amazing Exquisite Radiant Mana].ID}||${Me.Song[Gift of Phantasmal Exquisite Radiant Mana].ID})
/noparse /declare Twincast string outer (${Me.Song[Twincast].ID}||${Me.Song[Twincast Rk. II].ID}||${Me.Song[TwincastRk. III].ID}||${Me.Song[Improved Twincast].ID}||${Me.Buff[Twincast].ID}||${Me.Buff[Improved Twincast].ID})
/noparse /declare Named string outer (${Target.Named}||${Target.Name.Find[#]} && !${Target.Master.ID})
/noparse /declare CombatConditions string outer (${Target.ID}==${TarID} && ${Target.PctHPs}<=98 && ${Target.LineOfSight})

/declare MedAt int outer 30
/declare MedTimer timer outer 5s
/declare TarID int outer
/declare CurrentSub string outer
/declare loop int outer
/declare ReAssist timer outer 2s

|Nukes
/declare Nuke1 string outer Claw of the Flamewing Rk. II
/noparse /declare NukeConditions1 string outer (!${Twincast})
/declare Nuke2 string outer Ethereal Weave Rk. II
/noparse /declare NukeConditions2 string outer (${Twincast}||${Me.Song[Elemental Flames].ID}||${GoM})
/declare Nuke3 string outer Ethereal Hoarfrost Rk. II
/noparse /declare NukeConditions3 string outer 1
/declare Nuke4 string outer Ethereal Incandescence Rk. II
/noparse /declare NukeConditions4 string outer 1
/declare Nuke5 string outer Ethereal Barrage Rk. II
/noparse /declare NukeConditions5 string outer 1
/declare Nuke6 string outer Force of Will
/noparse /declare NukeConditions6 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})
/declare Nuke7 string outer Force of Flame
/noparse /declare NukeConditions7 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})
/declare Nuke8 string outer Force of Fire
/noparse /declare NukeConditions8 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})

|Jolts
/declare Jolt1 string outer A Hole in Space
/noparse /declare JoltConditions1 string outer (${Me.PctHPs<70||${Me.PctAggro}>90)
/declare Jolt2 string outer Concussive Intuition
/noparse /declare JoltConditions2 string outer (${Me.PctAggro}>70)
/declare Jolt3 string outer Arcane Whisper
/noparse /declare JoltConditions3 string outer (${Named} && !${Me.Song[Silent Casting].ID})
/declare Jolt4 string outer Mind Crash
/noparse /declare JoltConditions4 string outer (${Named} && ${Me.PctAggro}>95)
/declare Jolt5 string outer Concussive Salvo Rk. II
/noparse /declare JoltConditions5 string outer (${Named} && ${Me.PctAggro}>85)

|Freeze
/declare Freeze string outer Skullfreeze Rk. II
/noparse /declare FreezeConditions string outer 1
/declare LastFreeze int outer

|AA
/declare AA1 string outer Empowered Focus of Arcanum
/noparse /declare AAConditions1 string outer 1
/declare AA2 string outer Improved Twincast
/noparse /declare AAConditions2 string outer (!${Twincast})
/declare AA3 string outer Twincast Rk. II
/noparse /declare AAConditions3 string outer (!${Twincast})
/declare AA4 string outer Prolonged Destruction
/noparse /declare AAConditions4 string outer (${Named})
/declare AA5 string outer Lower Element
/noparse /declare AAConditions5 string outer (${Named})
/declare AA6 string outer Distorted Robe of the Frozen Flame
/noparse /declare AAConditions6 string outer 1
/declare AA7 string outer Fury of Ro
/noparse /declare AAConditions7 string outer 1
/declare AA8 string outer Fundament: Second Spire of Arcanum
/noparse /declare AAConditions8 string outer 1

|Harvest
/declare Harvest1 string outer Bucolic Harvest Rk. II
/noparse /declare HarvestConditions1 string outer (!${Me.Invis} && ${Me.PctMana}<=65)
/declare Harvest2 string outer Harvest of Druzzil
/noparse /declare HarvestConditions2 string outer (!${Me.Invis} && ${Me.PctMana}<=65)
/declare Harvest3 string outer Summoned: Large Modulation Shard
/noparse /declare HarvestConditions3 string outer (!${Me.Invis} && ${Me.PctMana}<=65 && ${Me.CurrentHPs}>24000)


|Buffs
/declare SelfBuff1 string outer Armor of the Stonescale
/noparse /declare SelfBuffConditions1 string outer (${Spell[Armor of the Stonescale].Stacks})
/declare SelfBuff2 string outer Improved Familiar
/noparse /declare SelfBuffConditions2 string outer (${Spell[Improved Familiar].Stacks})

|snare
/declare LastSnare int outer
/declare Snare string outer Atol's Unresistable Shackles
/noparse /declare SnareConditions string outer (${Range.Between[0,25:${Target.PctHPs}]}

/return
Step 3. Add the assist name, amount, and command so that you can launch the macro. /mac wiz petesampras 95, default value is group tank and 95%
Also add the /call Initialize to load the variables
Code:
#turbo 80
#include spell_routines.inc

Sub Main(string assist,int amount)
/declare Assist string outer ${If[${assist.Length},${assist},${Group.MainTank}]}
/declare AssistAt int outer ${If[${amount},${amount},95]}
/call Initialize
:mainloop
/doevents
/call Target


/goto :mainloop
/return
Step 4. Add the /calls for the other routines.
Code:
#turbo 80
#include spell_routines.inc

Sub Main(string assist,int amount)
/declare Assist string outer ${If[${assist.Length},${assist},${Group.MainTank}]}
/declare AssistAt int outer ${If[${amount},${amount},95]}
/call Initialize
:mainloop
/doevents
/call Target
/if (${Me.CombatState.NotEqual[COMBAT]}) /call Rest
/if (${CombatConditions}) /call Combat
/goto :mainloop
/return

Sub Rest
/call Always SelfBuff
/call Always Harvest
/call Med
/return

Sub Combat
/call Once Freeze
/call Always AA
/call Always Jolt
/call Always Nuke
/call Once Snare
/return
Step 5. Actually write the routines.
Here is a basic assist routine:
Code:
Sub Target
/if ((!${Target.ID} || ${Target.Type.Equal[PC]} || !${ReAssist}) && ${SpawnCount[${Assist} radius 300]}) {
/assist ${Assist}
/varset ReAssist ${ReAssist.OriginalValue}
}
/if (${Target.ID} && (${Target.Type.Equal[NPC]}||${Target.Master.Type.Equal[NPC]}) && ${Target.ID}!=${TarID}) {
/varset TarID ${Target.ID}
}
/return
Here is a generic routine to cast spells/AA/items on you or your target that you dont have to worry about recast time:
Code:
Sub Instant(subname,int force)
/varset CurrentSub ${subname}
/declare i int local
/for i 1 to 20
/if (!${Defined[${subname}${i}]}) /return
/if (${${subname}Conditions${i}}||!${Defined[${subname}Conditions${i}]}) {
    /if (${FindItem[${${subname}${i}}].InvSlot} && !${FindItem[${${subname}${i}}].Timer}) /call Cast "${${subname}${i}}" item ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
    /if (${Me.AltAbilityReady[${${subname}${i}}]}) /call Cast "${${subname}${i}}" alt ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]} CheckAggro
    /if (${Me.SpellReady[${${subname}${i}}]}||${Me.Book[${${subname}${i}}]} && ${force} && !${Me.AltAbility[${${subname}${i}}]}) /call Cast "${${subname}${i}}" ${DefaultGem} ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]} CheckAggro
    }
/next i
/return
Here is a generic casting routine that you do care about the recast timer and dont want to use it as soon its available again:
Code:
Sub Duration(subname,int force)
/varset CurrentSub ${subname}
/declare i int local
/for i 1 to 20
/if (!${Defined[${subname}${i}]}) /return
/if ((!${${CurrentSub}Recast${i}}||${Last${CurrentSub}${i}}!=${Target.ID}) && (${${subname}Conditions${i}}||!${Defined[${subname}Conditions${i}]})) {
    /if (${FindItem[${${subname}${i}}].InvSlot} && !${FindItem[${${subname}${i}}].Timer}) {
                /call Cast "${${CurrentSub}${i}}" item ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
                    /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                        /varset Last${CurrentSub}${i} ${Target.ID}
                        /varset ${CurrentSub}Recast${i} ${${CurrentSub}Recast${i}.OriginalValue}
                    }
                }
    /if (${Me.AltAbilityReady[${${subname}${i}}]}) {
            /call Cast "${${CurrentSub}${i}}" alt ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]} CheckAggro
                        /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                            /varset Last${CurrentSub}${i} ${Target.ID}
                            /varset ${CurrentSub}Recast${i} ${${CurrentSub}Recast${i}.OriginalValue}
                }
            }
    /if (${Me.SpellReady[${${subname}${i}}]}||${Me.Book[${${subname}${i}}]} && ${force} && !${Me.AltAbility[${${subname}${i}}]}) {
            /call Cast "${${CurrentSub}${i}}" ${DefaultGem} ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]} CheckAggro
                /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                    /varset Last${CurrentSub}${i} ${Target.ID}
                    /varset ${CurrentSub}Recast${i} ${${CurrentSub}Recast${i}.OriginalValue}
                }
            }
    }
/next i

/return
Here is a generic aggro check that is checked every cast:
Code:
Sub CheckAggro
/if (${CurrentSub.Equal[Jolt]}) /return
/for loop 1 to 20
/if (!${Defined[Jolt${loop}]}) /goto :skip
/if (${subname}Conditions${loop}}) {
    /if (${FindItem[${Jolt${loop}}].InvSlot} && !${FindItem[${Jolt${loop}}].Timer}) {
        /call Interrupt
        /call Cast "${Jolt${loop}}" item
    }
    /if (${Me.AltAbilityReady[${Jolt${loop}}]}) {
        /call Interrupt
        /alt act ${Me.AltAbility[${Jolt${loop}}].ID}
    }
    /if (${Me.SpellReady[${Jolt${loop}}]}) {
        /call Interrupt
        /call Cast "${Jolt${loop}}" gem1 5s CheckAggro
    }
/next loop
:skip
/return
Generic sub to cast something once per mob
Code:
Sub Once(subname)
/if (${Last${subname}==${Target.ID}) /return
/varset CurrentSub ${subname}
/if (!${Defined[${subname}]}) /goto :skip
/if (${subname}Conditions}) {
    /if (${FindItem[${${subname}}].InvSlot} && !${FindItem[${${subname}}].Timer}) /call Cast "${${subname}}" item
    /if (${Me.AltAbilityReady[${${subname}}]}) /call Cast "${${subname}}" alt
    /if (${Me.SpellReady[${${subname}}]}) /call Cast "${${subname}}" gem1 5s CheckAggro
    /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) /varset Last${CurrentSub} ${Target.ID}
/next loop
:skip
/return
Generic med routine:
Code:
Sub Med
  /if (${Me.PctMana}<${MedAt} && !${MedTimer} && !${Me.Mount.ID} && ${Me.State.Equal[STAND]} && (${Me.CombatState.Equal[ACTIVE]}||${Me.CombatState.Equal[DEBUFFED]} && !${Debuff.Count})) {
          /sit
        /varset MedTimer ${MedTimer.OriginalValue}
        }
/return
Here is a sub for things you want to cast on groupmates that you may or may not have a recast issue. Ie. Heals, HoTs, delayed heals, Buffs.
Code:
Sub Group(subname,int force)
/varset CurrentSub ${subname}
/declare i int local
/declare x int local

/for x 1 to 20
/if (!${Defined[${CurrentSub}${x}]}) /return
    /for i 0 to 5
        /if (${${CurrentSub}${x}${Group.Member[${i}].ID}}||!${Group.Member[${i}].ID}) /goto :skip
        /if (${${CurrentSub}Conditions${x}}||!${Defined[${CurrentSub}Conditions${x}]}) {
        /if (${Spell[${${CurrentSub}${x}}].TargetType.NotEqual[self]}) /squelch /tar id ${Group.Member[${i}].ID}
        /delay 1s ${Target.ID}==${Group.Member[${i}].ID}
            /if (${FindItem[${${CurrentSub}${x}}].InvSlot} && !${FindItem[${${CurrentSub}${x}}].Timer}) {
                /call Cast "${${CurrentSub}${x}}" item ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
                    /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                        /if (!${Defined[${CurrentSub}${x}${Group.Member[${i}].ID}]}) /declare ${CurrentSub}${x}${Group.Member[${i}].ID} timer outer
                        /if (${Defined[${CurrentSub}Recast${x}]}) /varset ${CurrentSub}${x}${Group.Member[${i}].ID} ${${CurrentSub}Recast${x}}
                    }
                }
            /if (${Me.AltAbilityReady[${${CurrentSub}${x}}]}) {
                /call Cast "${${CurrentSub}${x}}" alt ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
                    /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                        /if (!${Defined[${CurrentSub}${x}${Group.Member[${i}].ID}]}) /declare ${CurrentSub}${x}${Group.Member[${i}].ID} timer outer
                        /if (${Defined[${CurrentSub}Recast${x}]}) /varset ${CurrentSub}${x}${Group.Member[${i}].ID} ${${CurrentSub}Recast${x}}
                    }
                }    
            /if (${Me.SpellReady[${${CurrentSub}${x}}]}||${Me.Book[${${CurrentSub}${x}}]} && ${force} && !${Me.AltAbility[${${CurrentSub}${x}}]}) {
                /call Cast "${${CurrentSub}${x}}" ${DefaultGem} ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
                    /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                        /if (!${Defined[${CurrentSub}${x}${Group.Member[${i}].ID}]}) /declare ${CurrentSub}${x}${Group.Member[${i}].ID} timer outer
                        /if (${Defined[${CurrentSub}Recast${x}]}) /varset ${CurrentSub}${x}${Group.Member[${i}].ID} ${${CurrentSub}Recast${x}}
                    }
                }                

        }
    :skip
    /next i
/next x
/return
EDITOR's NOTE: The errors are fixed in the final example, but not in the other sub sections. Im too lazy.
 

Attachments

  • Spell_routines.inc
    29 KB · Views: 52
  • wiz.mac
    13.7 KB · Views: 131
  • mag.mac
    17.6 KB · Views: 67
  • Nec.mac
    21.3 KB · Views: 58
Last edited:
Sweet, this will help me perfect my own macro!

My biggest issue was figuring out how to set conditions as variables... of course I finally figured that out yesterday (/noparse) and here it is fully explained, but oh well lol.

As always, thanks Pete!
 
Fixed it up courtesy of testing with Dealings. This is about as efficient as I can think of for a wizard macro. No frills, pure dps.

To answer your question @ /noparse.

Go into game and type:
/echo ${Me.PctHPs}

Result:
100

now type:
/noparse /echo ${Me.PctHPs}

Result:
${Me.PctHPs}

If you were to declare something with a TLO/member in it, it would save it as the current value, instead of as the actual TLO/member.
 
Incredible DPS , fast efficient, wow is all I can say if you have a lvl 100 wiz w good aa you wont find a better macro.

Thank you Pete this is exactly what I wanted.
 
in grp content w bard in grp 200k+ named
sustained varies due to procs its a huge spectrum of difference

no procs to proc almost every nuke 30s 40s to 200k+

again dont think anything comes close to this macro for speed or output (and f yea aggro management if needed)
 
in grp content w bard in grp 200k+ named
sustained varies due to procs its a huge spectrum of difference

no procs to proc almost every nuke 30s 40s to 200k+

again dont think anything comes close to this macro for speed or output (and f yea aggro management if needed)

That is what some people seem to miss when they go with a all in one macro. You will never have the speed and response as you would if you did straight class based.

Good to see this thread get started.
 
There is certainly a trade off for speed and capability. A DPS class will almost certainly perform better with a very specifically tailored macro. Hybrids and priests probably lose the least. The main perks with the multi-class/bot macros are data standardization and ease of use combod with wide array of capabilities. But a tailored DPS mac that uses chain events will crush almost anything out there.

Having to "learn" a new macro for every character can get tedious, but if you write one specifically for your personal need and tuned around your actual gear, spells, and AAs, then it will probably perform a lot better than something someone else wrote that wasnt designed specifically for you. Sometimes performing at 75% or 90% is good enough though. I only used my bot.mac personally, but I also know it inside and out and can tweak it exactly how I want on the fly. Not everyone has that ability.
 
EQUser:
What I did for this particular macro was to remove the need for gems/alt/etc by checking and trying whatever it finds.

Code:
Sub Always(subname)
/varset CurrentSub ${subname}
/for loop 1 to 20
/if (!${Defined[${subname}${loop}]}) /goto :skip
/if (${${subname}Conditions${loop}}) {
    /if (${FindItem[${${subname}${loop}}].InvSlot} && !${FindItem[${${subname}${loop}}].Timer}) /call Cast "${${subname}${loop}}" item
    /if (${Me.AltAbilityReady[${${subname}${loop}}]}) /alt act ${Me.AltAbility[${${subname}${loop}}].ID}
    /if (${Me.SpellReady[${${subname}${loop}}]}) /call Cast "${${subname}${loop}}" gem1 5s CheckAggro
    }
/next loop
:skip
/return

So then all you have to do is designate a Word# and a matching WordConditions# then /call Always Word and it will use it.

So I could write:
/declare Potato1 string outer Elixir of the Beneficent
/noparse /declare PotatoConditions1 string outer (!${Me.Song[Elixir of the Beneficent].ID} && ${Me.CombatState.Equal[COMBAT]})

then:
/call Always Potato

But I do see some room for improvement in the code to see if you have conditions or not. Previously I have been using the:
/noparse /declare PotatoConditions1 string outer 1
That way the ${PotatoConditions1} would return 1 and actually work. But I can make the check so that potato conditions either exists and is true, or doesnt exist.

Code:
Sub Always(subname)
/varset CurrentSub ${subname}
/for loop 1 to 20
/if (!${Defined[${subname}${loop}]}) /goto :skip
/if (${${subname}Conditions${loop}}[COLOR=MediumTurquoise]||!${Defined[${subname}Conditions${loop}]}[/COLOR]) {
     /if (${FindItem[${${subname}${loop}}].InvSlot} &&  !${FindItem[${${subname}${loop}}].Timer}) /call Cast  "${${subname}${loop}}" item
    /if (${Me.AltAbilityReady[${${subname}${loop}}]}) /alt act ${Me.AltAbility[${${subname}${loop}}].ID}
    /if (${Me.SpellReady[${${subname}${loop}}]}) /call Cast "${${subname}${loop}}" gem1 5s CheckAggro
    }
/next loop
:skip
/return

So that can change the Sub Initialize for nukes from:
Code:
Nukes
/declare Nuke1 string outer Claw of the Flamewing Rk. II
/noparse /declare NukeConditions1 string outer (!${Twincast})
/declare Nuke2 string outer Ethereal Weave Rk. II
/noparse /declare NukeConditions2 string outer (${Twincast}||${Me.Song[Elemental Flames].ID}||${GoM})
/declare Nuke3 string outer Ethereal Hoarfrost Rk. II
/noparse /declare NukeConditions3 string outer 1
/declare Nuke4 string outer Ethereal Incandescence Rk. II
/noparse /declare NukeConditions4 string outer 1
/declare Nuke5 string outer Ethereal Barrage Rk. II
/noparse /declare NukeConditions5 string outer 1
/declare Nuke6 string outer Force of Will
/noparse /declare NukeConditions6 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})
/declare Nuke7 string outer Force of Flame
/noparse /declare NukeConditions7 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})
/declare Nuke8 string outer Force of Fire
/noparse /declare NukeConditions8 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})

To:
Code:
Nukes
/declare Nuke1 string outer Claw of the Flamewing Rk. II
/declare Nuke2 string outer Ethereal Weave Rk. II
/declare Nuke3 string outer Ethereal Hoarfrost Rk. II
/declare Nuke4 string outer Ethereal Incandescence Rk. II
/declare Nuke5 string outer Ethereal Barrage Rk. II
/declare Nuke6 string outer Force of Will
/declare Nuke7 string outer Force of Flame
/declare Nuke8 string outer Force of Fire
/noparse /declare NukeConditions1 string outer (!${Twincast})
/noparse /declare NukeConditions2 string outer (${Twincast}||${Me.Song[Elemental Flames].ID}||${GoM})
/noparse /declare NukeConditions6 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})
/noparse /declare NukeConditions7 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})
/noparse /declare NukeConditions8 string outer (!${Me.SpellReady[${Me.Gem[1]}]} && !${Me.SpellReady[${Me.Gem[2]}]} && !${Me.SpellReady[${Me.Gem[3]}]})

It cuts out the Conditions that arent needed. saves a little coding space, and makes it cleaner. I will add the change to the mac.
 
That rearrange got me thinking about why wizard is so easy to code for. There are no dots or heals or really anything other than buffing yourself or using non-duration, non-recasting spells. Mindfreeze and snare are used once per mob in the example. But what if you wanted to cast mindfreeze over and over? or if you wanted to use a dot on another class? So I wrote a third sub routine and changed the name of Always to Instant.

-Sub Instant will be for spells that dont have reuse/recast considerations for your target
-Sub Duration will be dots/debuffs/short reuse things that you dont want to cast again until they wear off. But you want to immediately reuse them on the next mob.
-Sub Once is for anything you want cast one time per mob.

So to do that, I want to add a few custom declares and check against them. In this case, I want to know what the last mob ID was that I cast the spell on, and I want to know what the recast is for my spell.

So I will write the name and the conditions declares in the same style as the others:
Code:
/declare Dot1 string outer Disease Cloud
/noparse /declare DotConditions1 string outer (${Range.Between[20,90:${Target.PctHPs}]})
But now I need some sort of variable to tell me a recast time and a last ID used against:
Code:
/declare DotRecast1 timer outer 60s
For the ID, I will use a loop, since it will be the same for any dot variable:
Code:
/for i 1 to 20
/if (${Defined[Dot${i}]}) /declare LastDot${i} int outer
/next i

This means my new variables are NameRecast# and LastName#

This will work with any dot/debuff/etc in order that I put them.

Now for the routine to process it and check those 2 new variables and /varset them once cast. It is almost a direct rip for the instant routine otherwise.
Code:
Sub Duration(subname)
/varset CurrentSub ${subname}
/for loop 1 to 10
/if (!${Defined[${subname}${loop}]}) /goto :skip
/if (!${${CurrentSub}Recast${loop}}||${Last${CurrentSub}${loop}}) && (${${subname}Conditions${loop}}||!${Defined[${subname}Conditions${loop}]})) {
    /if (${FindItem[${${subname}${loop}}].InvSlot} && !${FindItem[${${subname}${loop}}].Timer}) /call Cast "${${subname}${loop}}" item
    /if (${Me.AltAbilityReady[${${subname}${loop}}]}) /alt act ${Me.AltAbility[${${subname}${loop}}].ID}
    /if (${Me.SpellReady[${${subname}${loop}}]}) /call Cast "${${subname}${loop}}" gem1 5s CheckAggro
            /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                    /varset Last${CurrentSub}${loop} ${Target.ID}
                    /varset ${CurrentSub}Recast${loop} ${${CurrentSub}Recast${loop}.OriginalValue}
            }
    }
/next loop
:skip
/return

And voila, now I can use Dots/Debuffs/blah duration based spells. This means I could now change Mindfreeze to a duration spell if I wanted to.

New format:
/call Duration Dot
/call Instant Nuke
/call Once Snare
 
I dont know why, but the Duration sub was cycling incorrectly. I had to change some variables around to get it to stop skipping numbers.

I added a dot section example for a level 100 necro and it works as intended.

I also added an example so that you can use color casting announcements like in Bot.mac. This wont change any of the cycle time of the macro, but will make it a lot easier to see what is going on. To add or change a color you would just do:
1. Use the spell_routines.inc from bot.mac thread.
2. Open it up and change:
/if (!${Defined[AnnounceSpellRoutines]}) /declare AnnounceSpellRoutines bool outer ${Ini[MySpellIni,Settings,AnnounceSpellRoutines,TRUE]}

3. Declare the color you want to use for each type. Examples:
/declare NukeColor string outer Red
/declare AAColor string outer Purple
/declare DotColor string outer Yellow

From now on, you will get color echos in the color of your choice that announce your spell as you cast it. ie.
Osalur's Flashblaze --> Target Name

A list of colors can be seen in the spell_routines, but its basically all standard colors + Dark Color. yellow, dark yellow. red, dark red. etc.
 
Last edited:
An easy pet attack string is:
/if (${TarID} && ${Me.Pet.ID} && ${Me.Pet.Following.ID}!=${TarID}) /pet attack
 
Correct. I /declared Named as a custom variable in the Sub Initialize to use as an easy substitution.

/noparse /declare Named string outer (${Target.Named}||${Target.Name.Find[#]} && !${Target.Master.ID})

Same for ${GoM} to see if you have any version of GoM on you and ${Twincast} to see if you have any version of Twincast on you. It is just a time saver. If you plan on using the same set of variables over and over and over, I highly suggest doing a /noparse declare as a shortcut.
 
For a necro, one thing you may want to consider for your NukeConditions is to not use any nukes until your dots are all on. Or at least not your short duration dots.

A method to look at just short duration dots (say they are Dot1-3)
Code:
/noparse /declare NukeConditions1 string outer (!${Me.GemTimer[${Me.Gem[${Dot1}]} && !${Me.GemTimer[${Me.Gem[${Dot2}]} && !${Me.GemTimer[${Me.Gem[${Dot3}])
But for the longer dots that dont get those same lockouts, you would have to make it a little longer and look at the timers. So a little more complex, but still doable:
Code:
/noparse /declare NukeConditions1 string outer (${LastDot1}==${Target.ID} && ${DotRecast1} && ${LastDot2}==${Target.ID} && ${DotRecast2} && ${LastDot3}==${Target.ID} && ${DotRecast3})
etc etc. Or you could just do 1 /noparse at the beginning of macro:
Code:
/noparse /declare DotsOn string outer  (${LastDot1}==${Target.ID} && ${DotRecast1} &&  ${LastDot2}==${Target.ID} && ${DotRecast2} &&  ${LastDot3}==${Target.ID} && ${DotRecast3} &&  ${LastDot4}==${Target.ID} && ${DotRecast4} &&  ${LastDot5}==${Target.ID} && ${DotRecast5} &&  ${LastDot6}==${Target.ID} && ${DotRecast6})
Then you could do:
Code:
/noparse /declare NukeConditions1 string outer (${DotsOn}||${Target.PctHPs}<20)
Then it would only cast nukes if all dots were on or mob was less than 20% HP. I'd opt for that last route, personally.
 
Ok, since newb was interested in a necro mac, I went ahead and wrote one for a level 100 with Full AAs. You may need to tweak the items used and maybe the rank of the pet buff. It seems that the pet buff absolutely required Rk. II to be added for me to get it to work. The rest worked fine listing them as rank 1s.

I also added an option for force memorizing spells if they arent already memorized. To force mem if not memmed, add a " 1" to the end of the call:
Code:
/call Instant SelfBuff 1
/call Instant Pet 1
I also created and set the default gem as 12 for personal use. change that to whatever you need.
Code:
/declare DefaultGem gem12

Finally, I created a routine that will let you do commands. Like send pets, or stand up or sit down or /echo something or /bc something, or whatever.

Code:
Sub Command(subname)
/varset CurrentSub ${subname}
/for loop 1 to 20
/if (!${Defined[${CurrentSub}${loop}]}) /goto :skip
/if (${${CurrentSub}Conditions${loop}}||!${Defined[${CurrentSub}Conditions${loop}]}) {
    /docommand ${${CurrentSub}${loop}}
    }
/next loop
:skip

/return

An example /declare and Conditions you would use would be:
Code:
/declare Command1 string outer /pet attack
/declare Command2 string outer /stand
/noparse /declare CommandConditions1 string outer (${TarID} && ${Target.ID} && ${Me.Pet.ID} && ${Me.Pet.Following.ID}!=${TarID} && ${Target.Distance}<100)
/noparse /declare CommandConditions2 string outer (${Me.PctHPs}>70 && ${Me.Feigning})

In the case of necro, this allows you to send pet and stand up when you feign.

In recap, there are now 4 processing sub routines:
Commands - process any command (you could technically run everything off this)
Instant - for spells that dont have a recast you want to use
Duration - for detrimental spells that have a recast
Once - for spells you want to cast once per mob

About the only thing left to show examples for are heals and buffs, and let me tell you, buffs is a pain in the ass to do right.
 
Dealings had asked about the event of when youre getting hit. Adding an event is straight forward. First you set your event:

#event Name1 "Exact phrase goes here"
#event Name2 "#*#phrase#*#"
#event Name3 "#1# phrase #2# #3#"
#event Name4 "#*# phrase #1#"

Scenario: Someone says to group, 'Exact phrase goes here'

Results:
Name1 doesnt trigger because Someone says to group, 'Exact phrase goes here' isnt the same as Exact phrase goes here
Name2 does trigger because "phrase" was found
Name3 triggers, with result of: ${EventArg1}=Someone says to group, "Exact, ${EventArg2}=goes, ${EventArg3}=here'
Name4 triggers with: ${EventArg1}=here'

So we can make an event for getting hit because we know the 2 partial phrases that will always appear when something tries or successfully hits us:
Code:
#event ImHit     "#*# YOU for #*#"
#event ImHit     "#*# YOU, but #*#"
Then we write in some ImHit variables for stuff to do if we get hit, in Sub Initialize:
Code:
/declare ImHit1 string outer Death's Effigy
/declare ImHit2 string outer Embalmer's Carapace
/declare ImHit3 string outer Harmshield
/declare ImHit4 string outer Improved Death Peace
/noparse /declare ImHitConditions1 string outer (${Me.PctHPs}<40)
/noparse /declare ImHitConditions2 string outer (${Me.PctHPs}<40 && ${Me.Standing})
/noparse /declare ImHitConditions3 string outer (${Me.PctHPs}<40 && !${Me.Buff[Embalmer's Carapace].ID}})
/noparse /declare ImHitConditions4 string outer (${Me.PctHPs}<40 && ${Me.Standing})
Then we write the actual event to execute when triggered. So again, this will only attempt to fire when we actually get hit.
Code:
Sub Event_ImHit
/declare i int local
/for i 1 to 20
/if (!${Defined[ImHit${i}]}) /return
/if (${ImHitConditions${i}}) {
    /if (${FindItem[${ImHit${i}}].InvSlot} && !${FindItem[${ImHit${i}}].Timer}) /call Cast "${ImHit${i}}" item ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
    /if (${Me.AltAbilityReady[${ImHit${i}}]}) /call Cast "${ImHit${i}}" alt ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
    /if (${Me.SpellReady[${ImHit${i}}]}) /call Cast "${ImHit${i}}" ${DefaultGem} ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]} CheckAggro
    /if (${Me.CombatAbilityReady[${ImHit${i}}]}) /call Cast "${ImHit${i}}" disc ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
}
/next i
/return
 
Last edited:
Thanks Pete, gonna try this tonight and post what results I get.
 
thats gonna be an insane necro if its as fast as wiz (even w nerf dildo coming for wiz)

the more u use this it goes something like this mob gets pulled , mob gets to hp to assist, crew assrapes mobs in a blink of an eye :p
 
thats gonna be an insane necro if its as fast as wiz (even w nerf dildo coming for wiz)

the more u use this it goes something like this mob gets pulled , mob gets to hp to assist, crew assrapes mobs in a blink of an eye :p


It worked fine in shards landing for about 20 minutes of testing. There are a couple changes I would make to the spell order after seeing it. Like I would add in the knock back root if mob was within say 40 distance, unless I had a tank in group. And I would probably change it so that the scintillate undead fires sooner so that slow can land sooner if I wasnt in a sham/enc group.

It was slowing all the mobs, but only after all the dots were on and the nuke had landed. But feign death/stand and dots/aa/nuke were all firing exactly as planned. Buffs/Pet/petbuffs/death bloom/blah blah were all working as intended.

I think I could use everything that I am able to do with bot other than pulling/camp/follow stuff. Considering the low resource for this mac, would be a decent trade-off for DPS chars that were sitting in one spot exping.
 
So I figured out a fairly efficient way to do buffs/heals/hot. It is brutally dumb in that it checks by ID and if that ID doesnt have it and it meets conditions, it gets it. So every time you would zone or restart macro, you would get all the buffs again if they met conditions. But it is done that way for efficiency and in case you would die it would rebuff you. Now onto the code. I set it up in the same way as the others.

First you need to declare your stuff. If you dont declare conditions or recast, it doesnt use/check them. I tested with a necro to only cast DMF on clerics, and on 90s recast, and it worked exactly as intended. Note the recast in case you wanted to use delayed heals or HoTs. You could use single target shaman panther, or group hots/panther or any short or long term buffs.
Code:
|Buffs
/declare BuffColor string outer Pink
/declare Buff1 string outer Perfected Dead Man Floating
/declare BuffRecast1 string outer 90s
/noparse /declare BuffConditions1 string outer (${Select[${Group.Member[${i}].Class.ShortName},CLR]} && ${Group.Member[${i}].PctHPs}>50)
Then you call the routine (with a force option to force memorize the spell in case of buffs).
Code:
/call Group Heal
/call Group Buff 1
Then there is the routine itself:
Code:
Sub Group(subname,int force)
/varset CurrentSub ${subname}
/declare i int local
/declare x int local

/for x 1 to 20
/if (!${Defined[${CurrentSub}${x}]}) /return
    /for i 0 to 5
        /if (${${CurrentSub}${x}${Group.Member[${i}].ID}}||!${Group.Member[${i}].ID}) /goto :skip
        /if (${${CurrentSub}Conditions${x}}||!${Defined[${CurrentSub}Conditions${x}]}) {
        /if (${Spell[${${CurrentSub}${x}}].TargetType.NotEqual[self]}) /squelch /tar id ${Group.Member[${i}].ID}
        /delay 1s ${Target.ID}==${Group.Member[${i}].ID}
            /if (${FindItem[${${CurrentSub}${x}}].InvSlot} && !${FindItem[${${CurrentSub}${x}}].Timer}) {
                /call Cast "${${CurrentSub}${x}}" item ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
                    /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                        /if (!${Defined[${CurrentSub}${x}${Group.Member[${i}].ID}]}) /declare ${CurrentSub}${x}${Group.Member[${i}].ID} timer outer
                        /if (${Defined[${CurrentSub}Recast${x}]}) /varset ${CurrentSub}${x}${Group.Member[${i}].ID} ${${CurrentSub}Recast${x}}
                    }
                }
            /if (${Me.AltAbilityReady[${${CurrentSub}${x}}]}) {
                /call Cast "${${CurrentSub}${x}}" alt ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
                    /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                        /if (!${Defined[${CurrentSub}${x}${Group.Member[${i}].ID}]}) /declare ${CurrentSub}${x}${Group.Member[${i}].ID} timer outer
                        /if (${Defined[${CurrentSub}Recast${x}]}) /varset ${CurrentSub}${x}${Group.Member[${i}].ID} ${${CurrentSub}Recast${x}}
                    }
                }    
            /if (${Me.SpellReady[${${CurrentSub}${x}}]}||${Me.Book[${${CurrentSub}${x}}]} && ${force} && !${Me.AltAbility[${${CurrentSub}${x}}]}) {
                /call Cast "${${CurrentSub}${x}}" ${DefaultGem} ${If[${Defined[${CurrentSub}Color]},${${CurrentSub}Color},Orange]}
                    /if (${Macro.Return.Equal[CAST_SUCCESS]}||${Macro.Return.Equal[CAST_NOTHOLD]}) {
                        /if (!${Defined[${CurrentSub}${x}${Group.Member[${i}].ID}]}) /declare ${CurrentSub}${x}${Group.Member[${i}].ID} timer outer
                        /if (${Defined[${CurrentSub}Recast${x}]}) /varset ${CurrentSub}${x}${Group.Member[${i}].ID} ${${CurrentSub}Recast${x}}
                    }
                }                

        }
    :skip
    /next i
/next x
/return
I couldnt think of any wiz buffs off the top of my head so i added the routine to theirs but didnt declare any or /call any. But this should round out being able to use 95% of most classes with just these few subroutines.
 
Last edited: