First Release Version. Version 1.1
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Charles Click 2024-10-20 00:42:59 -04:00
parent aa1c31fffd
commit 33960f8ed9
Signed by: roryejinn
GPG Key ID: EDECB89DEDB998C0
14 changed files with 2387 additions and 73 deletions

View File

@ -3,12 +3,12 @@ steps:
image: git.vertinext.com/roryejinn/alpine_muddler image: git.vertinext.com/roryejinn/alpine_muddler
when: when:
- path: - path:
include: [ 'Fighter/*' ] include: [ 'src/*' ]
event: [push, pull_request] event: [push, pull_request]
environment: environment:
GITEATKN: GITEATKN:
from_secret: GITEA_TOKEN from_secret: GITEA_TOKEN
VERSION: 1.0.5 VERSION: 1.1
commands: commands:
- apk add tea - apk add tea
- java -jar /muddle/muddle.jar - java -jar /muddle/muddle.jar

View File

@ -3,26 +3,28 @@ Fighter is a simple, alias based PVP system for Achaea. The Fighter Engine is of
## Fighter Engine ## Fighter Engine
### Base Components ### 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. The Fighter Engine consists of all code in the aliases folder (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 ### 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. 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. 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.
#### Getting The Requirements
* AK 8.5: https://github.com/AranosBanazir/Legacy/blob/main/AK.xml
* Romaen's Limb Tracker: https://github.com/27theo/limb/releases/tag/v1.3
## Creating Your Own Class Engines ## 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. 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 ### 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. 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 three 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. 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. 2) 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.
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. 3) 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.
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 ### 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. 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.

2
mfile
View File

@ -1,5 +1,5 @@
{ {
"package": "Fighter", "package": "Fighter",
"description": "A simple, modular, alias based pvp engine written in Lua for Achaea.", "description": "A simple, modular, alias based pvp engine written in Lua for Achaea.",
"version": "1.0.5" "version": "1.1"
} }

View File

@ -5,8 +5,8 @@
"regex": "^fight$" "regex": "^fight$"
}, },
{ {
"name": "automateFight", "name": "showHelp",
"isActive": "yes", "isActive": "yes",
"regex": "^autofight$" "regex": "^fighthelp$"
} }
] ]

View File

@ -1,7 +0,0 @@
if fighter.automate then
cecho("\n<firebrick>COMBAT 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("\n<firebrick>COMBAT NOTICE: Will automate sending the next pvp command.")
fighter.automate = true
end

1
src/aliases/showHelp.lua Normal file
View File

@ -0,0 +1 @@
fighter.generateHelp()

View File

@ -0,0 +1,48 @@
--[===[
The MIT License (MIT)
Copyright (c) 2020 Damian Monogue
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]===]
-- schema validation provided by schema.lua, license below
--[[
The MIT License (MIT)
Copyright (c) 2014 Sebastian Schoener
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

View File

@ -0,0 +1,91 @@
# the demonnic MDK and You
This is a collection of Lua 'classes' and modules I wrote for Mudlet. It is largely targeted at scripters, and comes packaged in two ways depending on how you intend to use/distribute your work. Please see [Installation](#installation) for more details
## Documentation
The [MDK wiki](https://github.com/demonnic/MDK/wiki) contains an entry for each module or class, as well as examples.
Starting with alpha2 of the MDK, the ldocs generated from code are included in the zipped releases. The current release's ldocs can always be viewed at <https://demonnic.github.io/mdk/current/>
## Installation
How you 'install' the MDK depends on how you intend to use it.
### I just want to install the MDK for my own personal use
You just want to get your hands on the goods, and aren't looking to use any MDK items in an exported package for sharing or anything like that.
Well, you are who the mdk mpackage is for! Download the MDK.mpackage from your desired release on the [Releases](https://github.com/demonnic/MDK/releases) page and install it in the package manager. The examples in the [wiki](https://demonnic.github.io/mdk/current/) are written with this in mind, and you would require the items you need as `local EMCO = require("MDK.emco")`
### I am a package author looking to include/use one of the MDK modules or classes in my package
You should download the `demonnic-MDK-<version>.zip` file for your desired release on the [Releases](https://github.com/demonnic/MDK/releases) page.
Inside are the individual .lua files for the modules and classes described in the [wiki](https://demonnic.github.io/mdk/current/) and [API docs](https://demonnic.github.io/mdk/current/).
You can include all of them if you wish, or only the ones you actually make use of. I ask that you include the LICENSE.lua or LICENSE-MDK.lua file (depending on the release) file in addition.
They should go in the root of your package, so that when your package is installed the files can be found at `getMudletHomeDir() .. "/<packagename>/emco.lua"`. You would then use `local EMCO = require("<mypackagename>.emco")`
So for example if your package name is "MySuperCoolPackage" and it installs to `getMudletHomeDir() .. "/MySuperCoolPackage/"` then you use `local EMCO = require("MySuperCoolPackage.emco")` and the emco.lua file should be at `getMudletHomeDir() .. "/MySuperCoolPackage/emco.lua"`
## Files (Modules/Classes)
These files contain the modules in the MDK. You only need to include those files which you intend to use, except as noted in the descriptions below.
If you include any of the modules from the MDK, you should also include LICENSE.lua or LICENSE-MDK.lua. It contains the licenses for my modules and for luaunit and lua-schema which are not my original works.
You should maybe also include demontools.lua, as it notes below several other of the MDK modules make use of items within it.
* aliasmgr.ua
* Object to manage tempAliases programmatically. <https://github.com/demonnic/MDK/wiki/AliasMgr>
* chyron.lua
* Label which moves a message across its face from right to left, like a stock ticker or the news chyrons. Documentation at <https://github.com/demonnic/MDK/wiki/Chyron>
* demontools.lua
* Collection of miscellaneous useful functions. You should include this file if you use the MDK, as several other modules make use of it. Include functions for converting c/d/hecho, html, and ansi colored strings between each other, mkdir_p, and some others. <https://github.com/demonnic/MDK/wiki/DemonTools>
* emco.lua
* EMCO. Documentation at <https://github.com/demonnic/MDK/wiki/EMCO> Will make use of LoggingConsole if loggingconsole.lua and demontools.lua are included
* figlet.lua
* Creates FIGlets from strings
* Reference package with multiple fonts and color gradients at <https://github.com/demonnic/figinator>
* ftext.lua
* basic fText. Documentation at <https://github.com/demonnic/MDK/wiki/fText>
* now includes TextFormatter and TableMaker as ftext.TextFormatter and ftext.TableMaker
* gradientmaker.lua
* Functions for creating color gradients for use with c/d/hecho. Documentation at <https://github.com/demonnic/MDK/wiki/GradientMaker>
* loggingconsole.lua
* Self logging extension to the mini console. Works just like a Geyser.MiniConsole but adds a templated path and fileName constraint, as well as logFormat so it can log what is echod or appended to it. Requires demontools.lua in order to work.
* loginator.lua
* Creates objects for logging messages to disk. <https://github.com/demonnic/MDK/wiki/Loginator>
* mastermindsolver.lua
* A class which will help you solve Master Mind puzzles. <https://github.com/demonnic/MDK/wiki/MasterMindSolver>
* revisionator.lua
* A class which aims to make upgrading between package versions easier by storing and running patch functions. <https://github.com/demonnic/MDK/wiki/Revisionator>
* sortbox.lua
* SortBox, an alternative to H/VBox which can be either, and also provides options for sorting its contents. Overview at <https://github.com/demonnic/MDK/wiki/SortBox>
* spinbox.lua
* SpinBox, a Geyser element for adjusting numbers with your mouse. Overview at <https://github.com/demonnic/MDK/wiki/SpinBox>
* sug.lua
* Self Updating Gauges, will watch a set of variables and update itself on a timer based on what values those variables hold. Documentation at <https://github.com/demonnic/MDK/wiki/SelfUpdatingGauge>
* textgauge.lua
* TextGauges, what it says on the tin. Documentation at <https://github.com/demonnic/MDK/wiki/TextGauge>
* timergauge.lua
* TimerGauge, an extension of Geyser.Gauge which serves as an animated countdown timer. Overview at <https://github.com/demonnic/MDK/wiki/TimerGauge>
## Others people's work I depend upon
* schema.lua
* lua-schema, for defining table schema. Documentation at <https://github.com/sschoener/lua-schema>
* will be used by Archon for ensuring configuration tables are as they should be.
* LICENSE.lua
* Contains the license information for MDK, as well as lua-schema and luaunit which have been included.

1697
src/resources/MDK/ftext.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,447 @@
local ftext = require("MDK.ftext")
describe("ftext:", function()
describe("ftext.fText:", function()
local fText = ftext.fText
it("Should properly center text", function()
local expected = " some text "
local actual = fText("some text", {width = 20})
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should properly pad left aligned text", function()
local expected = "some text "
local actual = fText("some text", {width = 20, alignment = "left"})
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should properly pad right aligned text", function()
local expected = " some text"
local actual = fText("some text", {width = 20, alignment = "right"})
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should wrap lines to the correct length", function()
local str = "This is a test of the emergency broadcast system. This is only a test"
local options = {width = 10, alignment = "centered"}
local actual = fText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(line:len(), 10)
end
options.width = 15
actual = fText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(line:len(), 15)
end
end)
describe("non-space spacer character:", function()
local str = "some text"
local options = {width = "20", alignment = "left", spacer = "="}
it("Should work with left align", function()
local expected = "some text =========="
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should work with right align", function()
local expected = "========== some text"
options.alignment = "right"
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should work with center align", function()
local expected = ("==== some text =====")
options.alignment = "center"
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
end)
describe("nogap option:", function()
local str = "some text"
local options = {width = "20", alignment = "left", spacer = "=", nogap = true}
it("Should work with left align", function()
local expected = "some text==========="
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should work with right align", function()
local expected = "===========some text"
options.alignment = "right"
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should work with center align", function()
local expected = "=====some text======"
options.alignment = "center"
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
end)
describe("cap functionality", function()
local str = "some text"
local options = {width = 20, spacer = "=", cap = "|"}
it("Should place the spacer outside the cap by default", function()
local expected = "===| some text |===="
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should place it inside the cap if inside option is true", function()
local expected = "|=== some text ====|"
options.inside = true
local actual = fText(str, options)
options.inside = nil
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should mirror certain characters with their opposites", function()
local expected = "===[ some text ]===="
options.mirror = true
options.cap = "["
local actual = fText(str, options)
assert.equals(expected, actual)
options.inside = true
expected = "[=== some text ====]"
actual = fText(str, options)
assert.equals(expected, actual)
options.inside = nil
options.cap = "<"
expected = "===< some text >===="
actual = fText(str, options)
assert.equals(expected, actual)
options.cap = "{"
expected = "==={ some text }===="
actual = fText(str, options)
assert.equals(expected, actual)
options.cap = "("
expected = "===( some text )===="
actual = fText(str, options)
assert.equals(expected, actual)
options.cap = "|"
expected = "===| some text |===="
actual = fText(str, options)
assert.equals(expected, actual)
end)
end)
end)
describe("ftext.cfText", function()
local cfText = ftext.cfText
local str = "some text"
local options = {
width = 20,
spacer = "=",
cap = "[",
inside = true,
mirror = true,
capColor = "<purple>",
spacerColor = "<green>",
textColor = "<red>",
}
it("Should handle cecho colored text", function()
local expectedStripped = "[=== some text ====]"
local expected = "<purple>[<reset><green>===<reset><red> some text <reset><green>====<reset><purple>]<reset>"
local actual = cfText(str, options)
local actualStripped = cecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
expectedStripped = "===[ some text ]===="
expected = "<green>===<reset><purple>[<reset><red> some text <reset><purple>]<reset><green>====<reset>"
options.inside = false
actual = cfText(str, options)
actualStripped = cecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
end)
it("Should wrap cecho lines to the correct length", function()
local str = "This is a test of the emergency broadcast system. This is only a test"
local options = {width = 10, alignment = "centered"}
local actual = cfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(cecho2string(line):len(), 10)
end
options.width = 15
actual = cfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(cecho2string(line):len(), 15)
end
end)
end)
describe("ftext.dfText", function()
local dfText = ftext.dfText
local str = "some text"
local options = {
width = 20,
spacer = "=",
cap = "[",
inside = true,
mirror = true,
capColor = "<160,32,240>",
spacerColor = "<0,255,0>",
textColor = "<255,0,0>",
}
it("Should handle decho colored text", function()
local expectedStripped = "[=== some text ====]"
local expected = "<160,32,240>[<r><0,255,0>===<r><255,0,0> some text <r><0,255,0>====<r><160,32,240>]<r>"
local actual = dfText(str, options)
local actualStripped = decho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
expectedStripped = "===[ some text ]===="
expected = "<0,255,0>===<r><160,32,240>[<r><255,0,0> some text <r><160,32,240>]<r><0,255,0>====<r>"
options.inside = false
actual = dfText(str, options)
actualStripped = decho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
end)
it("Should wrap decho lines to the correct length", function()
local str = "This is a test of the emergency broadcast system. This is only a test"
local options = {width = 10, alignment = "centered"}
local actual = dfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(decho2string(line):len(), 10)
end
options.width = 15
actual = dfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(decho2string(line):len(), 15)
end
end)
end)
describe("ftext.hfText", function()
local hfText = ftext.hfText
local str = "some text"
local options = {
width = 20,
spacer = "=",
cap = "[",
inside = true,
mirror = true,
capColor = "#a020f0",
spacerColor = "#00ff00",
textColor = "#ff0000",
}
it("Should handle hecho colored text", function()
local expectedStripped = "[=== some text ====]"
local expected = "#a020f0[#r#00ff00===#r#ff0000 some text #r#00ff00====#r#a020f0]#r"
local actual = hfText(str, options)
local actualStripped = hecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
expectedStripped = "===[ some text ]===="
expected = "#00ff00===#r#a020f0[#r#ff0000 some text #r#a020f0]#r#00ff00====#r"
options.inside = false
actual = hfText(str, options)
actualStripped = hecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
end)
it("Should wrap hecho lines to the correct length", function()
local str = "This is a test of the emergency broadcast system. This is only a test"
local options = {width = 10, alignment = "centered"}
local actual = hfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(hecho2string(line):len(), 10)
end
options.width = 15
actual = hfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(hecho2string(line):len(), 15)
end
end)
end)
describe("ftext.TextFormatter", function()
local tf = ftext.TextFormatter
local str = "some text"
local formatter
before_each(function()
formatter = tf:new({width = 20})
end)
it("Should let you change width using :setWidth", function()
formatter:setWidth(80)
local expected =
"<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
assert.equals(80, cecho2string(actual):len())
end)
it("Should format for cecho by default", function()
local expected = "<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
local expectedStripped = " some text "
local actual = formatter:format(str)
local actualStripped = cecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
end)
it("Should produce the same line as cfText given the same options", function()
local expected = ftext.cfText(str, formatter.options)
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should let you change type using :setType", function()
formatter:setType("h")
local expected = ftext.hfText(str, formatter.options)
local actual = formatter:format(str)
assert.equals(expected, actual)
formatter:setType("d")
expected = ftext.dfText(str, formatter.options)
actual = formatter:format(str)
assert.equals(expected, actual)
formatter:setType("")
expected = ftext.fText(str, formatter.options)
actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should default to word wrapping, and let you change it with :setWrap", function()
formatter:setWidth(10)
local expected =
"<white><reset><white> <reset><white> some <reset><white> <reset><white><reset>\n<white><reset><white> <reset><white> text <reset><white> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
expected = "<white><reset><white><reset><white> some text <reset><white><reset><white><reset>"
formatter:setWrap(false)
actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the cap using :setCap", function()
formatter:setCap('|')
local expected = "<white>|<reset><white> <reset><white> some text <reset><white> <reset><white>|<reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the capColor using :setCapColor", function()
formatter:setCapColor('<red>')
local expected = "<red><reset><white> <reset><white> some text <reset><white> <reset><red><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the spacer color using :setSpacerColor", function()
formatter:setSpacerColor("<red>")
local expected = "<white><reset><red> <reset><white> some text <reset><red> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the text color using :setTextColor", function()
formatter:setTextColor("<red>")
local expected = "<white><reset><white> <reset><red> some text <reset><white> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the spacer using :setSpacer", function()
formatter:setSpacer("=")
-- local expected = "<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
local expected = "<white><reset><white>====<reset><white> some text <reset><white>=====<reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to set the alignment using :setAlignment", function()
formatter:setAlignment("left")
local expected = "<white><reset><white><reset><white>some text <reset><white> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
formatter:setAlignment("right")
expected = "<white><reset><white> <reset><white> some text<reset><white><reset><white><reset>"
actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the 'inside' option using :setInside", function()
formatter:setInside(false)
local expected = "<white> <reset><white><reset><white> some text <reset><white><reset><white> <reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the mirror option using :setMirror", function()
formatter:setCap('<')
formatter:setMirror(true)
local expected = "<white><<reset><white> <reset><white> some text <reset><white> <reset><white>><reset>"
local actual = formatter:format(str)
assert.equal(expected, actual)
end)
end)
describe("ftext.TableMaker", function()
local TableMaker = ftext.TableMaker
local tm
before_each(function()
tm = TableMaker:new()
tm:addColumn({name = "col1", width = 15, textColor = "<red>"})
tm:addColumn({name = "col2", width = 15, textColor = "<blue>"})
tm:addColumn({name = "col3", width = 15, textColor = "<green>"})
tm:addRow({"some text", "more text", "other text"})
tm:addRow({"little text", "bigger text", "text"})
end)
it("Should assemble a formatted table given default options", function()
local expected = [[<white>*************************************************<reset>
<white>*<reset><white><reset><white> <reset><red> col1 <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> col2 <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> col3 <reset><white> <reset><white><reset><white>*<reset>
<white>*<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>*<reset>
<white>*<reset><white><reset><white> <reset><red> some text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> more text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> other text <reset><white> <reset><white><reset><white>*<reset>
<white>*<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>*<reset>
<white>*<reset><white><reset><white> <reset><red> little text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> bigger text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> text <reset><white> <reset><white><reset><white>*<reset>
<white>*************************************************<reset>
]]
local actual = tm:assemble()
assert.equals(expected, actual)
end)
it("TableMaker:getCell should return the text and formatter for a specific cell", function()
local expectedText = "more text"
local expectedFormatter = tm.columns[2]
local actualText, actualFormatter = tm:getCell(1, 2)
assert.equals(expectedText, actualText)
assert.equals(expectedFormatter, actualFormatter)
local expectedFormatted = "<white><reset><white> <reset><blue> more text <reset><white> <reset><white><reset>"
local actualFormatted = actualFormatter:format(actualText)
assert.equals(expectedFormatted, actualFormatted)
end)
end)
end)

View File

@ -0,0 +1 @@
2.10.0

View File

@ -1,10 +1,10 @@
-- The alchemist implementation variables -- The alchemist implementation variables
alchemist = alchemist or {} alchemist = alchemist or {}
alchemist.pid = nil
alchemist.specialty = nil alchemist.specialty = nil
alchemist.balances = alchemist.balances or {} alchemist.balances = alchemist.balances or {}
alchemist.balances.homunculus = true alchemist.balances.homunculus = true
alchemist.balances.humor = true alchemist.balances.humor = true
alchemist.humors = alchemist.humors or {}
alchemist.humors.table = { alchemist.humors.table = {
"Sanguine", "Sanguine",
"Sanguine", "Sanguine",
@ -22,12 +22,12 @@ alchemist.inundate = false
-- NOTE: This script was written for a Sublimation user and primarily pushes keeping class specific weaknesses and using bleeding/aurify as a killpath -- 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() function alchemist.decideAction()
if gmcp.Char.Status.class ~= "Alchemist" then if gmcp.Char.Status.class ~= "Alchemist" then
cecho("\n<firebrick>(BadPVP): Called Alchemist decision engine when not a Alchemist.") cecho("\n<firebrick>COMBAT NOTICE: Called Alchemist decision engine when not a Alchemist.")
return return
end end
if not target or target == nil then if not target or target == nil or target == "None" or target == "Dude" then
cecho("\n<firebrick>(BadPVP): Called decision engine without a target.") cecho("\n<firebrick>COMBAT NOTICE: Called decision engine without a target.")
return return
end end
@ -40,10 +40,10 @@ function alchemist.decideAction()
local affsReport = {} local affsReport = {}
local enemyClass = nil local enemyClass = nil
local humors = { local humors = {
choleric = ak.alchemist.humour.choleric or 0, Choleric = ak.alchemist.humour.choleric or 0,
melancholic = ak.alchemist.humour.melancholic or 0, Melancholic = ak.alchemist.humour.melancholic or 0,
phlegmatic = ak.alcehmist.humour.phlegmatic or 0, Phlegmatic = ak.alchemist.humour.phlegmatic or 0,
sanguine = ak.alchemist.humour.sanguine or 0 Sanguine = ak.alchemist.humour.sanguine or 0
} }
if table.contains(Legacy.CT.Enemies, target) then if table.contains(Legacy.CT.Enemies, target) then
@ -77,12 +77,12 @@ function alchemist.decideAction()
table.insert(commandString, "homunculus corrupt " .. target) table.insert(commandString, "homunculus corrupt " .. target)
table.insert(affsReport, "homunculus corruption") table.insert(affsReport, "homunculus corruption")
alchemist.balances.homunculus = false alchemist.balances.homunculus = false
alchemist.hopid = tempTimer(12, alchemist.resetBalance("homunculus")) tempTimer(12, function() alchemist.resetBalance("homunculus") end)
else else
-- Just attack -- Just attack
table.insert(commandString, "homunculus attack " .. target) table.insert(commandString, "homunculus attack " .. target)
alchemist.balances.homunculus = false alchemist.balances.homunculus = false
alchemist.hopid = tempTimer(3, alchemist.resetBalance("homunculus")) tempTimer(3, function() alchemist.resetBalance("homunculus") end)
end end
end end
@ -90,20 +90,20 @@ function alchemist.decideAction()
-- temper target humour (1.7 seconds Humor) -- temper target humour (1.7 seconds Humor)
-- inundate target humor (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 -- 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 if humors.Sanguine > 2 then
-- push inundate at 3+ sanguine to push bleeding -- push inundate at 3+ sanguine to push bleeding
table.insert(commandString, "inundate " .. target .. " sanguine") table.insert(commandString, "inundate " .. target .. " sanguine")
table.insert(affsReport, "bleeding") table.insert(affsReport, "bleeding")
alchemist.hupid = tempTimer(1.7, alchemist.resetBalance("humor")) tempTimer(1.7, function() alchemist.resetBalance("humor") end)
alchemist.inundate = true alchemist.inundate = true
elseif humors.phlegmatic >= 4 then elseif humors.Phlegmatic >= 4 then
-- For some reason, they allowed us to push phlegmatic very high so inundate this for extra affs -- 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(commandString, "inundate " .. target .. " phlegmatic")
table.insert(affsReport, "lethargy") table.insert(affsReport, "lethargy")
if humors.phlegmatic >= 4 then table.insert(affsReport, "anorexia") end if humors.Phlegmatic >= 4 then table.insert(affsReport, "anorexia") end
if humors.phlegmatic >= 6 then table.insert(affsReport, "slickness") end if humors.Phlegmatic >= 6 then table.insert(affsReport, "slickness") end
if humors.phlegmatic >= 8 then table.insert(affsReport, "weariness") end if humors.Phlegmatic >= 8 then table.insert(affsReport, "weariness") end
alchemist.hupid = tempTimer(1.7, alchemist.resetBalance("humor")) tempTimer(1.7, function() alchemist.resetBalance("humor") end)
else else
-- Temper whatever humor we have -- Temper whatever humor we have
local ourHumor = alchemist.humors.table[alchemist.humors.index] local ourHumor = alchemist.humors.table[alchemist.humors.index]
@ -111,12 +111,12 @@ function alchemist.decideAction()
table.insert(affsReport, "tempered" .. ourHumor .. "(" .. tostring(humors[ourHumor] + 1) .. ")") table.insert(affsReport, "tempered" .. ourHumor .. "(" .. tostring(humors[ourHumor] + 1) .. ")")
alchemist.humors.index = alchemist.humors.index + 1 alchemist.humors.index = alchemist.humors.index + 1
if alchemist.humors.index > #alchemist.humors.table then alchemist.humors.index = 1 end if alchemist.humors.index > #alchemist.humors.table then alchemist.humors.index = 1 end
alchemist.hupid = tempTimer(1.7, alchemist.resetBalance("humor")) tempTimer(1.7, function() alchemist.resetBalance("humor") end)
end end
-- Educe -- Educe
local hpPercent = ((gmcp.Char.Vitals.hp / gmcp.Char.Vitals.maxhp) * 100) local hpPercent = ((gmcp.Char.Vitals.hp / gmcp.Char.Vitals.maxhp) * 100)
if alchemist.wrackCount ~= 0 and alchemist.wrackcount % 3 ~= 0 then if alchemist.wrackCount ~= 0 and alchemist.wrackCount % 3 ~= 0 then
-- Educe here as we aren't truewracking -- Educe here as we aren't truewracking
if ak.defs.shield then if ak.defs.shield then
-- Break Shield -- Break Shield
@ -161,9 +161,6 @@ function alchemist.decideAction()
send("queue addclearfull eqbal murder") send("queue addclearfull eqbal murder")
send("pt " .. target .. " hit with " .. table.concat(affsReport, " ")) send("pt " .. target .. " hit with " .. table.concat(affsReport, " "))
if fighter.automate then
alchemist.pid = registerAnonymousEventHandler("gmcp.Char.Vitals", "alchemist.isReady")
end
end end
-- Functioned used to reset special class balances for Alchemist -- Functioned used to reset special class balances for Alchemist
@ -171,14 +168,11 @@ function alchemist.resetBalance(which)
if which == "homunculus" then if which == "homunculus" then
alchemist.balances.homunculus = true alchemist.balances.homunculus = true
cecho("\n<firebrick>COMBAT NOTICE: Homunculus balance restored.") cecho("\n<firebrick>COMBAT NOTICE: Homunculus balance restored.")
killTimer(alchemist.hopid)
elseif which == "humor" then elseif which == "humor" then
alchemist.balances.humor = true alchemist.balances.humor = true
cecho("\n<firebrick>COMBAT NOTICE: Humor balance restored.") cecho("\n<firebrick>COMBAT NOTICE: Humor balance restored.")
killTimer(alchemist.hupid)
else else
cecho("\n<firebrick>COMBAT NOTICE: We were asked to reset the Alchemist balance " .. cecho("\n<firebrick>COMBAT NOTICE: We were asked to reset the Alchemist balance " .. which .. " but that isn't a valid balance.")
which .. " but that isn't a valid balance.")
end end
end end
@ -198,22 +192,9 @@ function alchemist.setSpecialty()
end end
if ourSpecialty == nil then if ourSpecialty == nil then
cecho( cecho("\n<firebrick>COMBAT 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.")
"\n<firebrick>COMBAT 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 else
alchemist.specialty = ourSpecialty alchemist.specialty = ourSpecialty
cecho("\n<firebrick>COMBAT NOTICE: Set alchemy specialty to " .. ourSpecialty) cecho("\n<firebrick>COMBAT NOTICE: Set alchemy specialty to " .. ourSpecialty)
end end
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

View File

@ -2,7 +2,6 @@ fighter = fighter or {}
function fighter.Setup() function fighter.Setup()
fighter.classes = {} fighter.classes = {}
fighter.automate = false
-- To add a new class, put the name here -- To add a new class, put the name here
fighter.classes.implemented = { fighter.classes.implemented = {
@ -63,5 +62,68 @@ function fighter.Setup()
-- This will set the variable alchemist.specialty to Sublimation or Formulation depending -- This will set the variable alchemist.specialty to Sublimation or Formulation depending
alchemist.setSpecialty() alchemist.setSpecialty()
end end
registerAnonymousEventHandler("sysLoadEvent", "fighter.Setup") registerAnonymousEventHandler("sysLoadEvent", "fighter.Setup")
function fighter.verifyInstall(_, name)
if name ~= "fighter" then return end
fighter.Setup()
end
registerAnonymousEventHandler("sysInstall", "fighter.verifyInstall")
function fighter.verifyUninstall(_, name)
if name ~= "fighter" then return end
fighter = nil
alchemist = nil
psion = nil
end
registerAnonymousEventHandler("sysUninstall", "fighter.verifyUninstall")
function fighter.generateHelp()
local tablemaker = require("Fighter.MDK.ftext").TableMaker
local formatter = require ("Fighter.MDK.ftext").TextFormatter
headerFormat = formatter:new({
width = 80,
cap = "<|",
capColor = "<DarkSlateBlue>",
textColor = "<white>",
spacerColor = "<steel_blue>",
inside = false,
spacer = " ",
alignment = "center",
mirror = true
})
helpTable = tablemaker:new({
title = "Fighter Help",
printTitle = false,
printHeaders = true,
frameColor = "<steel_blue>",
separateRows = false,
forceHeaderSeparator = true
})
helpTable:addColumn({
name = "Command",
width = 30,
textColor = "<DarkSlateBlue>"
})
helpTable:addColumn({
name = "Description",
width = 50,
textColor = "<DarkSlateGrey>"
})
--pvp
helpTable:addRow({
"fight",
"Generate your next pvp action."
})
--pvpnt
helpTable:addRow({
"<LightSlateBlue>fighthelp",
"<LightSlateGrey>Display this menu."
})
cecho(headerFormat:format("Fighter Help Docs"))
cecho(helpTable:assemble())
end

View File

@ -1,18 +1,17 @@
-- Psion Implementation Variables -- Psion Implementation Variables
psion = psion or {} psion = psion or {}
psion.timeToFlurry = false psion.timeToFlurry = false
psion.pid = nil
-- The primary function to build pvp actions for the psion class -- 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 -- 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() function psion.decideAction()
if gmcp.Char.Status.class ~= "Psion" then if gmcp.Char.Status.class ~= "Psion" then
cecho("\n<firebrick>(BadPVP): Called Psion decision engine when not a Psion.") cecho("\n<firebrick>COMBAT NOTICE: Called Psion decision engine when not a Psion.")
return return
end end
if not target or target == nil then if not target or target == nil or target == "None" or target == "Dude" then
cecho("\n<firebrick>(BadPVP): Called decision engine without a target.") cecho("\n<firebrick>COMBAT NOTICE: Called decision engine without a target.")
return return
end end
@ -187,11 +186,3 @@ function psion.decideAction()
psion.pid = registerAnonymousEventHandler("gmcp.Char.Vitals", "psion.isReady") psion.pid = registerAnonymousEventHandler("gmcp.Char.Vitals", "psion.isReady")
end end
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