I just came back to RoM at the insistence of a few friends, so bear with me while I make updates to this thread, and updates to my old code. Apparently this server and the GM team here actually do things, so I have been told, which is why I return to this game. I have found my old DIYCE files and my macros on an old hard drive, so I will be making updates here as time goes on...Stay tuned my friends.
Installation Instructions:
QuoteDisplay MoreNote that this does not automate your combat (sorry bot lovers), it just picks the best attack to execute every time you hit the macro. You'll have to hit the macro each time you want your character to perform an action.
INSTALLATION:
If there are errors in your code, you'll get an icon around your minimap that is flashing red. Click on that to get the exact error message.
Here's the path you should create/navigate to:
Chronicles of Arcadia\Interface\Addons
Next create a new text file, save it as a .lua file, and name it CustomeFunctions. Do the same for a file naming it DIYCE. At this point you should have two files named CustomFunctions.lua and DIYCE.lua. Next, create a text file and save it as a .toc and name it DIYCE.
To verify you have it setup correctly, you will wind up with 3 files (DIYCE.toc, DIYCE.lua, and CustomFunctions.lua) in this folder:
Runes of Magic\Interface\Addons\DIYCE
Here is the code in the DIYCE.lua file:
-- DIY Combat Engine version 2.2local g_skill = {}local g_lastaction = ""-- Holds the created timerslocal DIYCE_Timers = {}function Msg(outstr,a1,a2,a3) DEFAULT_CHAT_FRAME:AddMessage(tostring(outstr),a1,a2,a3)endfunction ReadSkills() g_skill = {} local skillname,slotfor page = 1,4 do slot = 1 skillname = GetSkillDetail(page,slot) repeat local a1,a2,a3,a4,a5,a6,a7,a8,skillusable = GetSkillDetail(page,slot) if skillusable then g_skill[skillname] = { ["page"] = page, ["slot"] = slot } end slot = slot + 1 skillname = GetSkillDetail(page,slot) until skillname == nil endend-- Read Skills on Log-In/Class Change/Level-Up local DIYCE_EventFrame = CreateUIComponent("Frame","DIYCE_EventFrame","UIParent") DIYCE_EventFrame:SetScripts("OnUpdate", [=[ DIYCE_TimerUpdate(elapsedTime) ]=] ) DIYCE_EventFrame:SetScripts("OnEvent", [=[ if event == "PLAYER_SKILLED_CHANGED" then ReadSkills() end ]=] ) DIYCE_EventFrame:RegisterEvent("PLAYER_SKILLED_CHANGED")function PctH(tgt) return (UnitHealth(tgt)/UnitMaxHealth(tgt))endfunction PctM(tgt) return (UnitMana(tgt)/UnitMaxMana(tgt))endfunction PctS(tgt) return (UnitSkill(tgt)/UnitMaxSkill(tgt))endfunction CancelBuff(buffname) local i = 1 local buff = UnitBuff("player",i)while buff ~= nil do if buff == buffname then CancelPlayerBuff(i) return true endi = i + 1 buff = UnitBuff("player",i) end return falseendfunction BuffList(tgt) local list = {} local buffcmd = UnitBuff local infocmd = UnitBuffLeftTimeif UnitCanAttack("player",tgt) then buffcmd = UnitDebuff infocmd = UnitDebuffLeftTime end-- There is a max of 100 buffs/debuffs per unit apparently for i = 1,100 do local buff, _, stackSize, ID = buffcmd(tgt, i) local timeRemaining = infocmd(tgt,i) if buff then -- Ad to list by name list[buff:gsub("(%()(.)(%))", "%2")] = { stack = stackSize, time = timeRemaining or 0, id = ID } -- We also list by ID in case two different buffs/debuffs have the same name. list[ID] = {stack = stackSize, time = timeRemaining or 0, name = buff:gsub("(%()(.)(%))", "%2") } else break end endreturn listendfunction CD(skillname) local firstskill = GetSkillDetail(2,1) if (g_skill[firstskill] == nil) or (g_skill[firstskill].page ~= 2) then ReadSkills() endif g_skill[skillname] ~= nil then local tt,cd = GetSkillCooldown(g_skill[skillname].page,g_skill[skillname].slot) return cd <= 0.4 elseif skillname == nil then return false else Msg("Skill not available: "..skillname) --Comment this line out if you do not wish to recieve this error message. return endendfunction MyCombat(Skill, arg1) local spell_name = UnitCastingTime("player") local talktome = ((arg1 == "v1") or (arg1 == "v2")) local action,actioncd,actiondef,actioncnt if spell_name ~= nil then if (arg1 == "v2") then Msg("- ["..spell_name.."]", 0, 1, 1) end return true endfor x,tbl in ipairs(Skill) do local useit = type(Skill[x].use) ~= "function" and Skill[x].use or (type(Skill[x].use) == "function" and Skill[x].use() or false) if useit then if string.find(Skill[x].name, "Action:") then action = tonumber((string.gsub(Skill[x].name, "(Action:)( *)(%d+)(.*)", "%3"))) _1,actioncd = GetActionCooldown(action) actiondef,_1,actioncnt = GetActionInfo(action) if GetActionUsable(action) and (actioncd == 0) and (actiondef ~= nil) and (actioncnt > 0) then if talktome then Msg("- "..Skill[x].name) end UseAction(action) return true end elseif string.find(Skill[x].name, "Custom:") then action = string.gsub(Skill[x].name, "(Custom:)( *)(.*)", "%3") if CustomAction(action) then return true end elseif string.find(Skill[x].name, "Item:") then action = string.gsub(Skill[x].name, "(Item:)( *)(.*)", "%3") if talktome then Msg("- "..Skill[x].name) end UseItemByName(action) return true elseif (Skill[x].ignoretimer or GetDIYCETimerValue(Skill[x].timer) == 0) and CD(Skill[x].name) then if talktome then Msg("- "..Skill[x].name) end CastSpellByName(Skill[x].name) StartDIYCETimer(Skill[x].timer) return true elseif string.find(Skill[x].name, "Pet Skill:") then action = string.gsub(Skill[x].name, "(Pet Skill:)( *)(%d+)(.*)", "%3") UsePetAction(action) if (arg1 == "v2") then Msg(Skill[x].name.." has been fully processed") end return true end end end if (arg1 == "v2") then Msg("- [IDLE]", 0, 1, 1) end return falseend--[[ Timer Update function ]]---- Tick down any active timersfunction DIYCE_TimerUpdate(elapsed) for k,v in pairs(DIYCE_Timers) do v.timeLeft = v.timeLeft - elapsed if v.timeLeft < 0 then v.timeLeft = 0 end endend--[[ Create a named timer ]]---- if the named timer already exists, this does nothing.function CreateDIYCETimer(timerName, waitTime) if not DIYCE_Timers[timerName] then DIYCE_Timers[timerName] = { timeLeft = 0, waitTime = waitTime } endend--[[ Set/reset waitTimer of an existing timer ]]---- if the timer doesn't exist, this does nothingfunction SetDIYCETimerDelay(timerName, waitTime) if DIYCE_Timers[timerName] then DIYCE_Timers[timerName].waitTime = waitTime endend--[[ Delete named timer ]]---- if the timer doesn't exist, this does nothing-- Not really needed, but added for completenessfunction DeleteDIYCETimer(timerName) if DIYCE_Timers[timerName] then DIYCE_Timers[timerName] = nil endend--[[ Get a timer's current time ]]---- if the timer doesn't exist, this returns 0function GetDIYCETimerValue(timerName) if timerName then return DIYCE_Timers[timerName] and DIYCE_Timers[timerName].timeLeft or 0 end return 0end--[[ Starts a timer ticking down ]]---- if timer doesn't exist, this does nothingfunction StartDIYCETimer(timerName) if timerName and DIYCE_Timers[timerName] then DIYCE_Timers[timerName].timeLeft = DIYCE_Timers[timerName].waitTime endendfunction CustomAction(action) if CD(action) then if IsShiftKeyDown() then Msg("- "..action) end g_lastaction = action CastSpellByName(action) return true else return false endendfunction BuffTimeLeft(tgt, buffname) local cnt = 1 local buff = UnitBuff(tgt,cnt)while buff ~= nil do if string.find(buff,buffname) then return UnitBuffLeftTime(tgt,cnt) end cnt = cnt + 1 buff = UnitBuff(tgt,cnt) endreturn 0endfunction BuffParty(arg1,arg2)-- arg1 = Quickbar slot # for targetable, instant-cast buff without a cooldown (eg. Amp Attack) for range checking.-- arg2 = buff expiration time cutoff (in seconds) for refreshing buffs, default is 45 seconds.local selfbuffs = { "Soul Bond", "Enhanced Armor", "Holy Seal" } local groupbuffs = { "Grace of Life", "Amplified Attack", "Angel's Blessing", "Essence of Magic", "Magic Barrier", "Blessed Spring Water", "Fire Ward", "Savage Blessing", "Concentration Prayer", "Shadow Fury" }local buffrefresh = arg2 or 45 -- Refresh buff time (seconds) local spell = UnitCastingTime("player") -- Spell being cast? local vocal = IsShiftKeyDown() -- Generate feedback if Shift key heldif (spell ~= nil) then return endif vocal then Msg("- Checking self buffs on "..UnitName("player")) end for i,buff in ipairs(selfbuffs) do if (g_skill[buff] ~= nil) and CD(buff) and (BuffTimeLeft("player",buff) <= buffrefresh) then if vocal then Msg("- Casting "..buff.." on "..UnitName("player")) end TargetUnit("player") CastSpellByName(buff) return end endif vocal then Msg("- Checking group buffs on "..UnitName("player")) end for i,buff in ipairs(groupbuffs) do if (g_skill[buff] ~= nil) and CD(buff) and (BuffTimeLeft("player",buff) <= buffrefresh) then if vocal then Msg("- Casting "..buff.." on "..UnitName("player")) end TargetUnit("player") CastSpellByName(buff) return end endfor num=1,GetNumPartyMembers()-1 do TargetUnit("party"..num) if GetActionUsable(arg1) and (UnitHealth("party"..num) > 0) then if vocal then Msg("- Checking group buffs on "..UnitName("party"..num)) end for i,buff in ipairs(groupbuffs) do if (g_skill[buff] ~= nil) and CD(buff) and (BuffTimeLeft("target",buff) <= buffrefresh) then if UnitIsUnit("target","party"..num) then if vocal then Msg("- Casting "..buff.." on "..UnitName("target")) end CastSpellByName(buff) return else if vocal then Msg("- Error: "..UnitName("target").." != "..UnitName("party"..num)) end end end end else if vocal then Msg("- Player "..UnitName("party"..num).." out of range or dead.") end end endif vocal then Msg("- Nothing to do.") endend
Much of how the DIYCE actually functions still works the same as in the original, just at a much more optimized pace, with a different setup to the code itself. One major change is this version will still tell you when a skill is not available, however the rest of the DIYCE will continue to operate. No longer stopping while in the middle of combat, possibly creating problems (eg. Death).
QuoteHOW IT WORKS:
For those that want to know, this is how the Engine operates: The ReadSkills() function gets run whenever you enter the game, level-up, or switch classes. It goes through your skill book and memorizes all of your skill numbering and stores it in a global table (g_skill) to be referenced by the CD() function. The CD() function checks the cooldown on whatever skill is passed to it and will return a value of true if the named skill is not on cooldown and false otherwise, so think of this as a "is the skill ready" check. The MyCombat() function goes through a table (called "Skill") that you define with a list of each action/skill you want to perform looking for the first one that's ready to be used and meets the criteria you specify for that skill.
The custom functions no longer require a separate function for each class combination you have. (PLEASE STOP MAKING NEW FUNCTIONS! WHEN YOU MAKE A NEW FUNCTION YOU COUNTERACT THE OPTIMIZATION I WORKED HARD TO GIVE YOU! I know everyone is used to doing it that way, but this is one of the key optimizations to this new version.) There is now one master function for every class to use, which means one macro for everyone. Every toon you create using this new version of DIYCE will have the exact same macro ( /run Killsequence() ), the only differences from one toon to the next will be the numbers that go inside the parenthesis for the use of potions or foods.
BUILDING YOUR CUSTOM SECTION OF COMBAT SCRIPT:
Again, you are no longer creating a custom function for each class combination you make, you will simply be editing the existing function to include a check to be ran for your particular class combo.
You can have as many checks for class combinations as you need (there are 48 total combinations in the game at this point in time). What your class combination check will do is define a table of skills and the criteria to use those skills. Here is an example of a skill table in DIYCE v2.0:
--Potions and buffsSkill = {{ name = "Savage Blessing", use = (pctEB1 >= .05) and ((not pbuffs['Savage Blessing']) or (pbuffs['Savage Blessing'].time <= 45)) },{ name = "Concentration Prayer", use = (pctEB1 >= .05) and ((not pbuffs['Concentration Prayer']) or (pbuffs ['Concentration Prayer'].time <= 45)) },{ name = "Intensification", use = (pctEB1 >= .05) and (not pbuffs['Intensification']) and boss and enemy },}--Combatif enemy thenSkill2 = {{ name = "Binding Silence", use = (silenceThis) },{ name = "Silence", use = (silenceThis) },{ name = "Weakening Seed", use = (pctEB1 >= .05) and boss and ((not tbuffs['Weakening Seed']) or (tbuffs['Weakening Seed'].time < 4)) },{ name = "Mother Nature's Wrath", use = (pctEB1 >= .05) },--{ name = "Briar Entwinement", use = (pctEB1 >= .05) and ((not tbuffs['Briar Entwinement']) or (tbuffs['Briar Entwinement'].time < 4)) },{ name = "Lightning", use = (pctEB1 >= .05) },{ name = "Fireball", use = (pctEB1 >= .05) },{ name = "Earth Arrow", use = (pctEB1 >= .05) },}end
In this example the Combat Engine is checking for the buffs "Savage Blessing", "Concentration Prayer", and "Intensification" weather there is an enemy target selected or not, and if those buffs are not applied then apply them. This set of checks is done both in and out of combat. Next the Combat Engine checks to see if there is an enemy targeted, and if so then start using the damage spells that are set up based on the criteria to the right of the "use" determinate.
For "Binding Silence" and "Silence" the Combat Engine is referencing the Silence List variable set at the top of the DIYCE.lua file. If one of those spells is being cast by the enemy target, then a silencing spell will be used. Next the Combat Engine checksif you have a boss mob targeted, and if so then it checks to see if it has the debuff from "Weakening Seed", if not apply it. The Combat Engine moves down the rest of the list in this manner.
For each line in the combat script sections, you will want to apply a check to make sure you have enough rage/mana/energy/focus to cast a particular spell. In the example above, this is a script for a druid/mage and since both classes use mana, the check is "(pctEB1 >= .05)", which checks to see if there is more than or equal to 5% mana.
No longer do you need to classify in your variables which form of fuel you are using for your skills to check against. In the KillSequence function the variables you will be using most often are already set (add to it the variables you will need that are not included, but be advised to not change the already existing ones). The energy/rage/focus/mana bars are now read like this:
local EnergyBar1 = UnitMana("player")local EnergyBar2 = UnitSkill("player")local pctEB1 = PctM("player")local pctEB2 = PctS("player")
Bar1 is the upper bar (the bar of your primary class). Bar2 is the lower bar (the bar of your secondary class. The way to use these in your combat section of the KillSequence function for rage/energy/focus would be to use the variable EnergyBar1 or EnergyBar2, since all three of those forms are based on a hard number, and will remain the same throughout game play. The way to use these in your combat section for mana would be to use either pctEB1 or pctEB2, since the amount of mana used for any given skill goes up as you increase the level of the skill, as well as the amount of mana you have is ever changing, thus it is better to use a percent rather than a static number.
To visualize what this is doing, the Skill table might look like this (with the cooldown check listed as well) in the middle of combat when you hit the macro:
(This table gets messed up due to the formatting of the forum.)
skill use (CD)
Savage Blessing false true
Concen. Prayer false true
Intensification false true
Lightning true true
Fireball false false
Earth Arrow true true
The MyCombat() function will go through this table until it finds the first skill with a true condition for "use" that isn't on cooldown. So in this case, "Lightning" will get executed. This does not cycle through the list executing the skills in order, instead it goes through the list every time you run it and finds the first skill that's ready. So you're building a skill priority list, not a sequence.
Notice that I have "Briar Entwinement" defined in the function but I've commented it out ("--" at the beginning of the line). So that way if I want to add it back into my rotation later, I just un-comment the line. If you want to rearrange the priority of the skills, you just cut and paste the entire line wherever you want it in the order.