From 411eab152e08f7613a6d38c26c571a8c41da847c Mon Sep 17 00:00:00 2001 From: ccubed Date: Sat, 19 Oct 2024 06:00:52 -0400 Subject: [PATCH] Initial Sync of the Fighter Engine --- .gitignore | 1 + .woodpecker.yml | 46 +++++++ README.md | 47 ++++++++ mfile | 5 + src/aliases/aliases.json | 12 ++ src/aliases/automateFight.lua | 7 ++ src/aliases/callAction.lua | 7 ++ src/scripts/alchemist.lua | 219 ++++++++++++++++++++++++++++++++++ src/scripts/fighter.lua | 68 +++++++++++ src/scripts/psion.lua | 197 ++++++++++++++++++++++++++++++ src/scripts/scripts.json | 14 +++ 11 files changed, 623 insertions(+) create mode 100644 .gitignore create mode 100644 .woodpecker.yml create mode 100644 README.md create mode 100644 mfile create mode 100644 src/aliases/aliases.json create mode 100644 src/aliases/automateFight.lua create mode 100644 src/aliases/callAction.lua create mode 100644 src/scripts/alchemist.lua create mode 100644 src/scripts/fighter.lua create mode 100644 src/scripts/psion.lua create mode 100644 src/scripts/scripts.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3a1239 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +docs/* \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..fdd599d --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,46 @@ +steps: + - name: build-Fighter + image: git.vertinext.com/roryejinn/alpine_muddler + when: + - path: + include: [ 'Fighter/*' ] + event: [push, pull_request] + environment: + GITEATKN: + from_secret: GITEA_TOKEN + VERSION: 1.0.5 + commands: + - apk add tea + - java -jar /muddle/muddle.jar + - tea login add --name=temp --url=https://git.vertinext.com/ --token=$GITEATKN + - tea releases create --asset "build/Fighter.mpackage" --asset "build/Fighter.xml" --tag $VERSION --title "Latest Compiled Release" + + - name: notify-Failure + image: codeberg.org/l-x/woodpecker-ntfy + when: + - status: [ failure ] + event: [push, pull_request] + settings: + url: https://gotify.vertinext.com/woodpecker + token: + from_secret: GOTIFY_TOKEN + title: ${CI} - ${CI_REPO_NAME} + actions: "http, Open Build, ${CI_PIPELINE_URL}, clear=true" + tags: warning + icon: https://woodpecker-ci.org/img/logo.svg + message: Failed to build Fighter Plugins + + - name: notify-Success + image: codeberg.org/l-x/woodpecker-ntfy + when: + - status: [ success ] + event: [push, pull_request] + settings: + url: https://gotify.vertinext.com/woodpecker + token: + from_secret: GOTIFY_TOKEN + title: ${CI} - ${CI_REPO_NAME} + actions: "http, Open Build, ${CI_PIPELINE_URL}, clear=true" + tags: white_check_mark + icon: https://woodpecker-ci.org/img/logo.svg + message: Built new release packages for Fighter Plugins \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9b8bcf --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Fighter +Fighter is a simple, alias based PVP system for Achaea. The Fighter Engine is of a simplistic design and only handles managing and automating pvp actions. It does not send actions to the game on your behalf. Your specific Class Engines will do this. This package consists of the Fighter engine itself and two additional scripts demonstrating how to create PVP systems for different classes. The requirements for each vary and are explained in further detail below. + +## Fighter Engine +### Base Components +The Fighter Engine consists of all code in the aliases folder (automatFight, callAction) and the primary engine script (fighter.lua) under scripts. These components have no other plugin requirements. They only require Mudlet OR that gmcp data is exposed via a variable named gmcp. Even then, that sole requirement only comes up under the callAction script and is easy to remove if unwanted. + +### Example PVP Scripts +alchemist.lua and psion.lua are two example PVP scripts that demonstrate how to create a PVP decision engine that works with the Fighter Engine. These two example scripts are available for Legacy and Orion depending on which curing system you use. Additionally, this implementation relies on AK 8.5 and Romaen's Limb Tracker 1.3. Other versions of Romaen's Limb Tracker will probably work as long as they don't change how limb damage is accessed (IE: limbs[target].hits[limb]). AK on the other hand is a large, varied script and I can't guarantee any compatibility with other versions. + +Additionally, each script demonstrates different use cases and implementations. Psion relies heavily on limb damage and shows how you might change your actions based on that. On the other hand, the alchemist example demonstrates how one might handle multiple balance timers. + +## Creating Your Own Class Engines +While I've included an example alchemist and psion class engine, you might not have the same priorities as me. You might also want to add other classes. It is easy to do so. + +### Step 1 - Write Your Class Engine +Create a new script with a name that helps you identify it. In that script, you will write your entire Class Engine. This file is responsible for deciding what actions to take, managing affliction reporting, sending the commands to the game, storing and managing data specific to the classes implementation, and managing any created temporary objects such as timers, triggers or aliases. Your Class Engine has to follow four rules but can otherwise be written however you want. + +1) You must have a decideAction() function. decideAction() is the primary function called by the Fighter Engine to generate a pvp command string. + +2) You must have an isReady() function. isReady() is responsible for making automation work. The isReady function should determine when a new action should be taken and then kill the temporary event handler spawned by decideAction and recall decideAction to generate a new command. + +3) When designing your Class Engine, enclose everything in a single table which will act like a class. You can see an example of this in both example scripts where functions are held within a table. This makes it easier for Fighter to manipulate the implementations and call the Class Engines. + +4) Every Class Engine should be written in such a way that when fighter.Setup() is called it is able to rebuild itself. fighter.Setup() is designed as a basic hot reload function and will recreate Fighter and all sub-components each time. This allows you to force Mudlet to update any changes you make to the Fighter Engine or Class Engines. + +### Step 2 - Add it to fighter.lua +Open up fighter.lua and at the top you'll see two tables that we need to modify slightly for your new Class Engine to work. + +This table is responsible for noting which Class Engines are available for us to use. This is an important distinction as you can have a Class Engine listed under implementations for testing, but leave it out of implemented so Fighter won't call it during PVP. +```lua +fighter.classes.implemented = { + "Psion", + "Alchemist" +} +``` + +This table is responsible for holding references to each Class Engine. The names on the left should be the same as the class name reported by `gmcp.Char.Status.class` since that is the variable used to pull the implementation. The Fighter Engine works by calling these references to reach the functions and data contained within. Please note that there are no quotes here. This syntax is not an error. This is how lua defines tables with named keys instead of standard indexes. +```lua +fighter.classes.implementations = { + Psion = psion, + Alchemist = alchemist +} +``` + +### Step 3 - Profit +That's it. You've added your custom Class Engine. Now when you type fight (the default alias to call the next action) while switched to this new class, Fighter will automatically call the decideAction() function under the correct Class Engine for you. \ No newline at end of file diff --git a/mfile b/mfile new file mode 100644 index 0000000..cf50455 --- /dev/null +++ b/mfile @@ -0,0 +1,5 @@ +{ + "package": "Fighter", + "description": "A simple, alias based pvp engine written in Lua for Achaea.", + "version": "1.0.5" +} \ No newline at end of file diff --git a/src/aliases/aliases.json b/src/aliases/aliases.json new file mode 100644 index 0000000..fe9de83 --- /dev/null +++ b/src/aliases/aliases.json @@ -0,0 +1,12 @@ +[ + { + "name": "callAction", + "isActive": "yes", + "regex": "^fight$" + }, + { + "name": "automateFight", + "isActive": "yes", + "regex": "^autofight$" + } +] \ No newline at end of file diff --git a/src/aliases/automateFight.lua b/src/aliases/automateFight.lua new file mode 100644 index 0000000..a8c62cb --- /dev/null +++ b/src/aliases/automateFight.lua @@ -0,0 +1,7 @@ +if fighter.automate then + cecho("\nCOMBAT NOTICE: Will no longer automate sending the next pvp command. Please use the pvp alias when you want to send a command string.") + fighter.automate = false +else + cecho("\nCOMBAT NOTICE: Will automate sending the next pvp command.") + fighter.automate = true +end diff --git a/src/aliases/callAction.lua b/src/aliases/callAction.lua new file mode 100644 index 0000000..420396c --- /dev/null +++ b/src/aliases/callAction.lua @@ -0,0 +1,7 @@ +local ourClass = gmcp.Char.Status.class + +if table.contains(fighter.classes.implemented, ourClass) then + fighter.classes.implementations[ourClass].decideAction() +else + cecho("\nCOMBAT NOTICE: No implementation for " .. ourClass) +end diff --git a/src/scripts/alchemist.lua b/src/scripts/alchemist.lua new file mode 100644 index 0000000..ae0cef8 --- /dev/null +++ b/src/scripts/alchemist.lua @@ -0,0 +1,219 @@ +-- The alchemist implementation variables +alchemist = alchemist or {} +alchemist.pid = nil +alchemist.specialty = nil +alchemist.balances = alchemist.balances or {} +alchemist.balances.homunculus = true +alchemist.balances.humor = true +alchemist.humors.table = { + "Sanguine", + "Sanguine", + "Choleric", + "Sanguine", + "Melancholic", + "Sanguine", + "Phlegmatic" +} +alchemist.humors.index = 1 +alchemist.wrackCount = 0 +alchemist.inundate = false + +-- The primary function to build pvp actions for the alchemist class +-- NOTE: This script was written for a Sublimation user and primarily pushes keeping class specific weaknesses and using bleeding/aurify as a killpath +function alchemist.decideAction() + if gmcp.Char.Status.class ~= "Alchemist" then + cecho("\n(BadPVP): Called Alchemist decision engine when not a Alchemist.") + return + end + + if not target or target == nil then + cecho("\n(BadPVP): Called decision engine without a target.") + return + end + + -- set alchemist specialty if blank + if gmcp.Char.Status.class == "Alchemist" and alchemist.specialty == nil then + alchemist.setSpecialty() + end + + local commandString = { "setalias murder stand" } + local affsReport = {} + local enemyClass = nil + local humors = { + choleric = ak.alchemist.humour.choleric or 0, + melancholic = ak.alchemist.humour.melancholic or 0, + phlegmatic = ak.alcehmist.humour.phlegmatic or 0, + sanguine = ak.alchemist.humour.sanguine or 0 + } + + if table.contains(Legacy.CT.Enemies, target) then + enemyClass = Legacy.CT.Enemies[target] + elseif table.contains(Legacy.NDB.db, target) then + enemyClass = Legacy.NDB.db[target].class + end + + -- Order: Homunculus Attack, Temper/Inundate, Educe/Wrack OR Truewrack <- That or a Killpath + -- Killpath Reave: At least two tempered targets, can attempt to reave (not used) + -- Killpath aurify: If HP/MP both < 60%, turn target to gold + if (tonumber(ak.manapercent) < 60) and (tonumber(ak.healthpercent) < 60) then + -- Unleash the Aurify! + -- This one should process immediately + table.insert(commandString, "aurify " .. target) + send(table.concat(commandString, "/")) + send("queue addclearfull eqbal murder") + send("pt Starting Aurify attempt on " .. target) + return + end + + -- Homunculus - Homunculus Balance (Skip if not on balance) + -- homunculus attack target (do damage) (3 seconds) + -- homunculus shriek target (remove focus balance) (12 seconds) (unused) + -- homunculus corrupt target (flips bleed/clot. Bleed now causes mana loss, and clotting causes damage) (12 seconds) + if alchemist.balances.homunculus then + local hpOver = tonumber(ak.healthpercent) > 60 + local mpOver = tonumber(ak.manapercent) > 60 + if (hpOver and not mpOver) or (mpOver and not hpOver) then + -- One resource is high, the other is low. Corrupt to flip usage. + table.insert(commandString, "homunculus corrupt " .. target) + table.insert(affsReport, "homunculus corruption") + alchemist.balances.homunculus = false + alchemist.hopid = tempTimer(12, alchemist.resetBalance("homunculus")) + else + -- Just attack + table.insert(commandString, "homunculus attack " .. target) + alchemist.balances.homunculus = false + alchemist.hopid = tempTimer(3, alchemist.resetBalance("homunculus")) + end + end + + -- Temper + -- temper target humour (1.7 seconds Humor) + -- inundate target humor (1.7 seconds humor) + -- This will rotate through the alchemist.humors.table and temper those humors in order until it is time to inundate + if humors.sanguine > 2 then + -- push inundate at 3+ sanguine to push bleeding + table.insert(commandString, "inundate " .. target .. " sanguine") + table.insert(affsReport, "bleeding") + alchemist.hupid = tempTimer(1.7, alchemist.resetBalance("humor")) + alchemist.inundate = true + elseif humors.phlegmatic >= 4 then + -- For some reason, they allowed us to push phlegmatic very high so inundate this for extra affs + table.insert(commandString, "inundate " .. target .. " phlegmatic") + table.insert(affsReport, "lethargy") + if humors.phlegmatic >= 4 then table.insert(affsReport, "anorexia") end + if humors.phlegmatic >= 6 then table.insert(affsReport, "slickness") end + if humors.phlegmatic >= 8 then table.insert(affsReport, "weariness") end + alchemist.hupid = tempTimer(1.7, alchemist.resetBalance("humor")) + else + -- Temper whatever humor we have + local ourHumor = alchemist.humors.table[alchemist.humors.index] + table.insert(commandString, "temper " .. target .. " " .. ourHumor) + table.insert(affsReport, "tempered" .. ourHumor .. "(" .. tostring(humors[ourHumor] + 1) .. ")") + alchemist.humors.index = alchemist.humors.index + 1 + if alchemist.humors.index > #alchemist.humors.table then alchemist.humors.index = 1 end + alchemist.hupid = tempTimer(1.7, alchemist.resetBalance("humor")) + end + + -- Educe + local hpPercent = ((gmcp.Char.Vitals.hp / gmcp.Char.Vitals.maxhp) * 100) + if alchemist.wrackCount ~= 0 and alchemist.wrackcount % 3 ~= 0 then + -- Educe here as we aren't truewracking + if ak.defs.shield then + -- Break Shield + table.insert(commandString, "educe copper " .. target) + elseif alchemist.inundate then + -- We inundated so we want to educe iron to push damage + -- We should only not do that when the target is shielded + table.insert(commandString, "educe iron" .. target) + alchemist.inundate = false + elseif hpPercent <= 40 then + -- Educe tin to give us a break + table.insert(commandString, "educe tin") + else + -- Just do damage + table.insert(commandString, "educe iron " .. target) + end + end + + -- Wrack + -- wrack target humor/affliction (2 seconds Bal) + -- truewrack target humor/affliction humor/affliction (2.8 Seconds Bal) + if alchemist.wrackCount ~= 0 and alchemist.wrackCount % 3 ~= 0 then + -- Simple Wrack + table.insert(commandString, "wrack " .. target .. " impatience") + table.insert(affsReport, "impatience") + else + -- Truewrack + local ourSecondAff = nil + if table.contains(fighter.classes.clumsiness, enemyClass) then ourSecondAff = "clumsiness" end + if table.contains(fighter.classes.weariness, enemyClass) then ourSecondAff = "weariness" end + if table.contains(fighter.classes.stupidity, enemyClass) then ourSecondAff = "stupidity" end + if table.contains(fighter.classes.haemophilia, enemyClass) then ourSecondAff = "haemophilia" end + if ourSecondAff == nil then ourSecondAff = "haemophilia" end + table.insert(commandString, "truewrack " .. target .. " impatience " .. ourSecondAff) + table.insert(affsReport, "impatience") + table.insert(affsReport, ourSecondAff) + end + alchemist.wrackCount = alchemist.wrackCount + 1 + + -- Send final command, Report afflictions + send(table.concat(commandString, "/")) + send("queue addclearfull eqbal murder") + send("pt " .. target .. " hit with " .. table.concat(affsReport, " ")) + + if fighter.automate then + alchemist.pid = registerAnonymousEventHandler("gmcp.Char.Vitals", "alchemist.isReady") + end +end + +-- Functioned used to reset special class balances for Alchemist +function alchemist.resetBalance(which) + if which == "homunculus" then + alchemist.balances.homunculus = true + cecho("\nCOMBAT NOTICE: Homunculus balance restored.") + killTimer(alchemist.hopid) + elseif which == "humor" then + alchemist.balances.humor = true + cecho("\nCOMBAT NOTICE: Humor balance restored.") + killTimer(alchemist.hupid) + else + cecho("\nCOMBAT NOTICE: We were asked to reset the Alchemist balance " .. + which .. " but that isn't a valid balance.") + end +end + +-- A function that sets our alchemy specialty so we can refer to this much faster later +function alchemist.setSpecialty() + local ourSpecialty = nil + for k, v in pairs(gmcp.Char.Skills.Groups) do + if v.name == "Sublimation" then + ourSpecialty = "Sublimation" + break + end + + if v.name == "Formulation" then + ourSpecialty = "Formulation" + break + end + end + + if ourSpecialty == nil then + cecho( + "\nCOMBAT NOTICE: No alchemy specialty found. If you have not yet embraced your class or you are not currently an Alchemist this is normal. Otherwise, this is an error.") + else + alchemist.specialty = ourSpecialty + cecho("\nCOMBAT NOTICE: Set alchemy specialty to " .. ourSpecialty) + end +end + +-- This function is responsible for automating the combat system +-- This is an example of one that handles multiple balance timers +function alchemist.isReady() + -- Skip if we are still off balance + if (gmcp.Char.Vitals.eq == 0) or (gmcp.Char.Vitals.bal == 0) then return end + -- We want to make sure we at least have humor balance (Which we should generally) + if not alchemist.balances.humor then return end + + killAnonymousEventHandler(alchemist.pid) + alchemist.decideAction() +end diff --git a/src/scripts/fighter.lua b/src/scripts/fighter.lua new file mode 100644 index 0000000..64b8057 --- /dev/null +++ b/src/scripts/fighter.lua @@ -0,0 +1,68 @@ +fighter = fighter or {} + +function fighter.Setup() + fighter.classes = {} + fighter.automate = false + + -- To add a new class, put the name here + fighter.classes.implemented = { + "Psion", + "Alchemist" + } + + -- Then in here, set the class name from above equal to the main object in that classes pvp script + -- Important Note: You always need a name.decideAction function as this is called to generate pvp actions + fighter.classes.implementations = { + Psion = psion, + Alchemist = alchemist + } + + -- Classes where we want to push stupidity + fighter.classes.stupidity = { + "Alchemist" + } + + -- Classes where we want to push clumsiness + fighter.classes.clumsiness = { + "Blademaster", + "Druid", + "Infernal", + "Monk", + "Paladin", + "Runewarden", + "Priest", + "Sentinel", + "Serpent", + "Shaman", + "Psion", + "Depthswalker" + } + + -- classes where we want to push weariness + fighter.classes.weariness = { + "Blademaster", + "Druid", + "Monk", + "Priest", + "Pariah", + "Occultist", + "Sentinel", + "Serpent" + } + + -- classes where we want to push confusion + fighter.classes.confusion = { + "Psion" + } + + -- classes where we want to push haemophilia + fighter.classes.haemophilia = { + "Magi" + } + + -- Here is an example of adding additional setup for your pvp systems + -- This will set the variable alchemist.specialty to Sublimation or Formulation depending + alchemist.setSpecialty() +end + +registerAnonymousEventHandler("sysLoadEvent", "fighter.Setup") diff --git a/src/scripts/psion.lua b/src/scripts/psion.lua new file mode 100644 index 0000000..f1354cb --- /dev/null +++ b/src/scripts/psion.lua @@ -0,0 +1,197 @@ +-- Psion Implementation Variables +psion = psion or {} +psion.timeToFlurry = false +psion.pid = nil + +-- The primary function to build pvp actions for the psion class +-- NOTE: This script primarily pushes for keeping specific class weaknesses applied and stacking unweaved body/mind until it can be inverted to spirit and flurried against +function psion.decideAction() + if gmcp.Char.Status.class ~= "Psion" then + cecho("\n(BadPVP): Called Psion decision engine when not a Psion.") + return + end + + if not target or target == nil then + cecho("\n(BadPVP): Called decision engine without a target.") + return + end + + local commandString = { "setalias murder stand" } + local affsReport = {} + local limbs = nil + local enemyClass = nil + local unwoven = {} + local psionFlags = {} + local preferredLeg = nil + local preferredArm = nil + + -- Preset flags + psionFlags.clumsiness = false + psionFlags.weariness = false + psionFlags.stupidity = false + + -- Check enemy class + if table.contains(Legacy.CT.Enemies, target) then + enemyClass = Legacy.CT.Enemies[target] + elseif table.contains(Legacy.NDB.db, target) then + enemyClass = Legacy.NDB.db[target].class + end + + -- check limb damage + if table.contains(lb, target) then limbs = lb[target] end + + -- Set preferred limbs + if limbs ~= nil then + local limbTable = { "right", "left" } + + if limbs.hits['left leg'] > limbs.hits['right leg'] then + preferredLeg = 'left' + elseif limbs.hits['left leg'] == limbs.hits['right leg'] then + preferredLeg = limbTable[math.random(#limbTable)] + else + preferredLeg = 'right' + end + + if limbs.hits['left arm'] > limbs.hits['right arm'] then + preferredArm = 'left' + elseif limbs.hits['left arm'] == limbs.hits['right arm'] then + preferredArm = limbTable[math.random(#limbTable)] + else + preferredArm = 'right' + end + end + + -- Set unweavings + if table.contains(ak.psion.unweaving, 'body') then unwoven.body = ak.psion.unweaving.body else unwoven.body = 0 end + if table.contains(ak.psion.unweaving, 'mind') then unwoven.mind = ak.psion.unweaving.mind else unwoven.mind = 0 end + if table.contains(ak.psion.unweaving, 'spirit') then unwoven.spirit = ak.psion.unweaving.spirit else unwoven.spirit = 0 end + + -- Weave Prepare + -- Disruption = paralysis + -- Laceration = haemophilia + -- Dazzle = clumsiness + -- Rattle = epilepsy + -- Vapours = asthma + -- Incisive = beats clumsiness + -- Order: Incisive for Clumsiness, Paralysis, Class Specific, Asthma, Haemophilia, Epilepsy, Clumsy + if Legacy.Curing.Affs['clumsiness'] then + -- We have clumsiness so use incisive + table.insert(commandString, "weave prepare incisive") + elseif not ak.check('paralysis', 100) then + table.insert(commandString, "weave prepare disruption") + table.insert(affsReport, "paralysis") + elseif enemyClass ~= nil and table.contains(fighter.classes.clumsiness, enemyClass) and not ak.check('clumsiness', 100) then + table.insert(commandString, "weave prepare dazzle") + table.insert(affsReport, "clumsiness") + psionFlags.clumsiness = true + elseif enemyClass ~= nil and table.contains(fighter.classes.haemophilia, enemyClass) and not ak.check('haemophilia', 100) then + table.insert(commandString, "weave prepare laceration") + table.insert(affsReport, "haemophilia") + elseif not ak.check('asthma', 100) then + table.insert(commandString, "weave prepare vapours") + table.insert(affsReport, "asthma") + elseif not ak.check('haemophilia', 100) then + table.insert(commandString, "weave prepare laceration") + table.insert(affsReport, "haemophilia") + elseif not ak.check('epilepsy', 100) then + table.insert(commandString, "weave prepare rattle") + table.insert(affsReport, "epilepsy") + elseif not ak.check('clumsiness', 100) then + table.insert(commandString, "weave prepare dazzle") + table.insert(affsReport, "clumsiness") + psionFlags.clumsiness = true + end + + -- We do not have transcend, but once we do decide psionics action here + -- examples: + -- If paralized, we can psi manipulate touch tree + -- we can excise if mana less than 30% on enemy + -- we can use psi splinter to break shields + -- we can use psi combustion to push bleeding + -- we can use psi expunge to cure impatience + -- we can use psi muddle on stupidity inflicted enemies + -- we can just straight up psi shatter them + + -- Weaving + if ak.defs.shield then + table.insert(commandString, "weave cleave " .. target) + elseif unwoven.mind == 0 then + table.insert(commandString, "weave unweave " .. target .. " mind") + table.insert(affsReport, "unweavingmind(1)") + elseif unwoven.body == 0 then + table.insert(commandString, "weave unweave " .. target .. " body") + table.insert(affsReport, "unweavingbody(1)") + elseif not psionFlags.clumsiness and enemyClass ~= nil and table.contains(fighter.classes.clumsiness, enemyClass) and not ak.check("clumsiness", 100) then + table.insert(commandString, "weave sever " .. target .. " " .. preferredArm) + table.insert(affsReport, "clumsiness") + psionFlags.clumsiness = true + elseif not psionFlags.weariness and enemyClass ~= nil and table.contains(fighter.classes.weariness, enemyClass) and not ak.check("weariness", 100) then + table.insert(commandString, "weave puncture " .. target .. " " .. preferredArm) + table.insert(affsReport, "weariness") + psionFlags.weariness = true + elseif not psionFlags.stupidity and enemyClass ~= nil and table.contains(fighter.classes.stupidity, enemyClass) and not ak.check("stupidity", 100) then + table.insert(commandString, "weave backhand " .. target) + table.insert(affsReport, "stupidity") + table.insert(affsReport, "dizziness") + psionFlags.stupidity = true + elseif not ak.check("nausea", 100) then + -- exsanguinate for nausea + table.insert(commandString, "weave exsanguinate " .. target) + table.insert(affsReport, "nausea") + if ak.bleeding >= 150 then table.insert(affsReport, "anorexia") end + elseif not ak.check("asthma", 100) then + -- deathblow for asthma + table.insert(commandString, "weave deathblow " .. target) + table.insert(affsReport, "asthma") + table.insert(affsReport, "bleeding") + elseif not ak.check("stupidity", 100) or not ak.check("dizziness", 100) then + -- backhand for stupidity and dizziness + table.insert(commandString, "weave backhand " .. target) + table.insert(affsReport, "stupidity") + table.insert(affsReport, "dizziness") + psionFlags.stupidity = true + elseif not ak.check("blackout", 100) and ak.check("prone", 100) then + -- lightsteal for blackout + table.insert(commandString, "weave lightsteal " .. target) + table.insert(affsReport, "trueblind") + table.insert(affsReport, "blackout") + elseif unwoven.body == 5 then + -- invert! + table.insert(commandString, "weave invert " .. target .. " body spirit") + table.insert(affsReport, "unweavingspirit(5)") + psion.timeToFlurry = true + elseif unwoven.mind == 5 then + -- invert! + table.insert(commandString, "weave invert " .. target .. " mind spirit") + table.insert(affsReport, "unweavingspirit(5)") + psion.timeToFlurry = true + elseif psion.timeToFlurry then + -- Unleash Flurry! + table.insert(commandString, "weave flurry " .. target) + psion.timeToFlurry = false + elseif unwoven.body < 5 then + table.insert(commandString, "weave unweave " .. target .. " body") + table.insert(affsReport, "unweavingbody(" .. tostring(unwoven.body + 1) .. ")") + elseif unwoven.mind < 5 then + table.insert(commandString, "weave unweave " .. target .. " mind") + table.insert(affsReport, "unweavingmind(" .. tostring(unwoven.mind + 1) .. ")") + end + + -- Send final command, Report afflictions + send(table.concat(commandString, "/")) + send("queue addclearfull eqbal murder") + send("pt " .. target .. " hit with " .. table.concat(affsReport, " ")) + + -- If we are automating, start automation + if fighter.automate then + psion.pid = registerAnonymousEventHandler("gmcp.Char.Vitals", "psion.isReady") + end +end + +-- This function is responsible for automating the combat system +function psion.isReady() + if (gmcp.Char.Vitals.eq == 0) or (gmcp.Char.Vitals.bal == 0) then return end + + killAnonymousEventHandler(psion.pid) + psion.decideAction() +end diff --git a/src/scripts/scripts.json b/src/scripts/scripts.json new file mode 100644 index 0000000..b2340f3 --- /dev/null +++ b/src/scripts/scripts.json @@ -0,0 +1,14 @@ +[ + { + "name": "fighter", + "isActive": "yes" + }, + { + "name": "psion", + "isActive": "yes" + }, + { + "name": "alchemist", + "isActive": "yes" + } +] \ No newline at end of file