This readme
branch is only for documentations and related tools. Please check other branches to view Triggernometry codes.
Check the main repo for Triggernometry documentations
This document is a summary for my edited parts since Triggernometry v1.1.7.3.
-
String functions
-
tofullwidth
/tohalfwidth
${func:tofullwidth:H1}
=H1
${func:tohalfwidth:H1}
=H1
-
toxivchar(combineDigits=false)
Convert the alphanumeric characters to the corresponding XIV-defined black box characters.
IfcombineDigits
, numbers between 10 and 31 would be converted to a single black box character. -
ord(separator=",")
Convert a string to a sequence of charcodes.
${func:ord:Test 1}
=84,101,115,116,32,49
${func:ord(-):Test 1}
=84-101-115-116-32-49
-
chr(joiner=",")
The reverse of
ord
.
${func:chr:84,101,115,116,32,49}
=Test 1
-
-
Normal expressions
_triggerpath
The full path of the fired trigger.
-
Math functions
-
L1 and L∞ distance functions
Definition of Ln-distance:
distance = (|x - x0| ^ n + |y - y0| ^ n + ...) ^ (1 / n)
L2-distance: The normal distance (Euclidean distance)
L1-distance: Manhattan distance
L∞-distance: Chebyshev distanceThe expressions could be written as:
l1d(...)
/manhattandistance(...)
l∞d(...)
/chebyshevdistance(...)
parameters: same as
distance()
- e.g.
l1d(x0, y0, x1, y1)
l1d(x0, y0, z0, x1, y1, z1)
If you need to check if 2 locations are close to each other very frequently,
l1d
is better thandistance
(less calculation).
-
-
New action type: ACT Interaction
Contains:
-
Start/End ACT Encounter (The original
End Encounter
is moved here and auto-converted) -
Enable/disable "Use Deucalion" (injection)
-
Enable/disable "Log All Network Data"
This feature could be very useful when SE suddenly uses some packets as the only way to figure out a mechanism, while that packet has not been parsed as log lines.
You can control this option during certain time and use
252
=0xFC
network packet log lines to fire your trigger. -
-
Allow to sort a folder in descending order
Related discussion: https://discord.com/channels/374517624228544512/1237066801876041783
-
Allow firing triggers outside developer mode
Some triggers need the users to manually fire them for self-testing.
This is better not considered as "developer" level of usage.
There are many people asking about "why I cannot fire the trigger" in Discord, which also shows this point.
-
Display error log count on the main UI
Change the "Error has been encountered" message on the UI to display the specific number of errors encountered since the last opening of the error page.
Also fix an issue where the error info would never display after being opened once despite subsequent errors.
Additionally, refactored the logging system from a single Queue to a dictionary keyed by DebugLevelEnum. This allows a cap of 30000 logs per type instead of a shared cap of 100000, preventing general logs like Info and Verbose from overshadowing older Error/Warning logs.
-
Enhanced double/triple-clicking selection in expression textboxes
-
Double-click:
-
Clicking on all types of brackets / $ / ¤:
Select the full expression with enclosed brackets, which could be very useful when writing long nested expressions and you cannot find where the paired bracket is;
-
Clicking on spaces:
Select the adjacent spaces in the same line;
-
Clicking on other characters:
Select the current "word" (the splitting is better than original).
-
-
Triple-click:
-
Single-line: Select all text
-
Multi-line: Select current line
-
-
-
Show more information of named callbacks in the state form
-
Registrant
-
Registration time
-
Last Invoked
-
-
Register Named Callbacks
The
registrant
argument has been added:RegisterNamedCallback(string name, CustomCallbackDelegate callback, object o, string registrant)
The original function could still be used, and the registrant name would be detected automatically.
-
Added a ReadonlyRichTextbox class to display text with customized format
public class Triggernometry.CustomControls.ReadonlyRichTextbox : RichTextbox
This rich textbox has a "transparent" background, and do not allow any kind of mouse interaction (just like a label).
-
Provide more information during import
-
When the import fails, show more detailed reason
-
Show the plugin version of the imported xml
-
Check the link for details.
-
Line breaks
⏎
is considered the same as a linebreak (considered as whitespace characters). So it should be trimmed when splitting arguments, unless it is surrounded by quotation marks.- e.g.
listvar.join("⏎")
should be used instead oflistvar.join(⏎)
.
Also, line breaks could also be used in actions and considered as a single character:
-
e.g. using the expression below to build a list
1, 2, 3
(the first char is a linebreak, which is used as the separator of the following text when building a list in the action):1 2 3
This allows multiline input and makes it easier to read when using a large amount of data to build a variable.
- e.g.
- More intelligent autofill:
- If a string with one or no
${...}
expressions was input in a textbox for variable names, it would be saved into a corresponding dynamic variable list; - If any input string contains a single-layer variable expressions like
${var:name}
, thename
would be save into the corresponding dynamic variable list; - If the user is typing after expressions like
${v:...
, or typing in an expreesion textbox for variable names, it would also check all the names in the dynamic list. - This feature is compatitable with all kinds of variables (session/persistent), text/image auras, and named callbacks.
- If a string with one or no
- Changed behaviour for Enter key in the ActionForm:
- When pressing Enter in a single-line mode expression textbox, it would change to multi-line mode;
- When pressing in multi-line mode, it would add a linebreak directly, and the next line would be added the same indent with the previous line.
- Color expressions
- Added 3 expression textboxes for the text aura's forecolor, backcolor and outlinecolor.
- Color expressions are the same as in the action descriptions, and would show the corresponding parsed color as the background color of the textbox:
aaccff
/#aaccff
acf
/#acf
192, 0, 18
- Those colors accept expressions, so it would be possible to dynamically change the colors based on some real values in a same action, without using several different actions with only the color different. (but for now, the action still needs to be executed to refresh the color) e.g. Changed the text color based on the 2 dragons' HP in DSR P6.
- The color selector is still kept, for providing users a direct way to select colors.
- Moved the
RealPlugin.WindowsUtils
class toTriggernometry.Utilities
namespace, and added the corresponding scripting API safety option - Added a ffxiv process handle (with all access):
Note: It is still safe because all APIs were still restricted. It could also be used in the future to read some entity properties which are not available in ACT / update too slowly in ACT (like coordination).
public IntPtr Triggernometry.Realplugin.plug.XivHandle
- Added 2 Json methods since the current scripting environment cannot access
System.Text.Json
directly:public static string Triggernometry.Interpreter.StaticHelpers.Serialize(object o, bool indent = true); public static T Triggernometry.Interpreter.StaticHelpers.Deserialize<T>(string s);
- Added an operator
°
, for example,180° = 3.14159...
- Added a function
isanglebetween(θ, θ1, θ2)
to determine if a given angleθ
is within the range fromθ1
toθ2
(in the direction of increasing angle, i.e. counter-clockwise in FFXIV coordinate system).- The input angles do not need to be normalized to [-pi, pi]. The output is 0 or 1.
isanglebetween(pi/4, 0, pi/2) = 1
isanglebetween(pi, pi/2, -pi/2) = 1
isanglebetween(0, pi/2, -pi/2) = 0
- Example: Determine if the character's heading is safe in a back-facing mechanism.
- The input angles do not need to be normalized to [-pi, pi]. The output is 0 or 1.
- In the
LogMessage
action, a new option "Add log to ACT combat record" has been added to facilitate browsing both regular and custom logs together in the ACT combat record.- e.g., A
1B
(HeadMarker) log with offset removed can be added to the combat record with a custom format, as if a log without offset was generated at the same time.
- e.g., A
- New entity attributes added:
subrole
androleid
(corresponding to the text and numeric values defined below). Related discussion
public enum RoleType
{
None = 0,
Tank = 8,
Healer = 16,
DPS = 24,
Crafter = 32,
Gatherer = 48,
MainRole = 56,
PureHealer = Healer | 1,
FlexHealer = Healer | 2,
BarrierHealer = Healer | 3,
StrengthMelee = DPS | 1,
DexterityMelee = DPS | 2,
PhysicalRanged = DPS | 4,
MagicalRanged = DPS | 6,
}
- Added a new query pattern for
_entity
:${_entity[key=value].prop}
- For example,
${_entity[bnpcid=12601].heading}
can be used to query the entity of BNpcId 12601. - It returns the first entity found. If no entity is found, it returns the data of NullCombatant.
- You can determine whether you are in the door boss based on the BNpcId of some characteristic entities.
- If you need more complex query conditions, or to query multiple entities that meet the conditions, you can use the Table action to query all entities, or use scripts directly:
List<VariableDictionary> Triggernometry.PluginBridges.BridgeFFXIV.GetAllEntities()
- For example,
- Added a static utility class
StaticHelpers
to the Interpreter to facilitate the use of methods that are independent of the Context instance. - All methods of
TriggernometryHelpers
are still available. - Example:
using Triggernometry.Variables;
using static Triggernometry.Interpreter;
class ConfigForm
{
//...
public void LoadFromConfig()
{
VariableDictionary config = StaticHelpers.GetDictVariable(isPersistent, configName);
// ...
}
}
-
Modified action descriptions for actions requiring specific window titles and
procid
, following the newly added logic aboutprocid
. -
Fixed several null reference exceptions, such as
ActionViewer.Actions = null
causing sporadic errors when clicking in the ActionForm interface, andRealPlugin.plug.currentZone = null
causing potential errors when editing group area restrictions with FFXIV not running. -
Renamed the
testmode
property totestByPlaceholder
to avoid ambiguity, due to previously added properties for testing actions. -
Renamed several incorrectly named
cbx...
checkboxes tochk...
. -
Other minor modifications.
-
ActionViewer
- Add
Copy Conditions
,Paste Conditions
,Test Action
in the right-click menu
- Add
-
ActionForm / Action
- Fix the null reference bug related with template trigger
- The log entries for exceptions occurring in actions will now include the corresponding descriptions and trigger path
- Added two actions:
- VariableScalar -
Increment
(for convenience) - VariableTable -
AppendH
(append tables horizontally)
- VariableScalar -
- Update the links to the keypress documentations and switch to the corresponding links for the selected language option
- Fix the issue that some table actions did not set their changer and time, such as
Resize
andCopy
-
Context
${estorage:xxx}
: check if the scripting storage contains the keyxxx
${_storage[xxx]}
: output the objects in the scripting storage${_configpath}
${_pluginpath}
: the corresponding local folder paths- Added
${_config[UnsafeUsage]}
; - Changed the
${_config[API]}
results to bitwise (0-7
,Local << 2 | Remote << 1 | Admin
)
-
RealPlugin
- Fix a bug where the version in
cfg.Constants
was not updated on the first launch after updating the plugin, but on the second launch - Expand the exceptions in named callbacks (delegates) to show their actual exception messages
- Added a
static RealPlugin RealPlugin.plug
for convenient referencing (all theRealPlugin
instances throughout the entire program are the same one).
- Fix a bug where the version in
-
Others
- Minor changes:
- Change
void Context.XxxxxError()
toException Context.XxxxxError()
- Change
I18n.TrlXxxxx()
to a single functionI18n.Trl("xxxxx")
- Change some
internal
topublic
for scripts - Translation fixes
- Change
- Minor changes:
-
ActionViewer
- Fixed a bug where some buttons in
ActionViewer
sometimes are not enabled/disabled correctly; - Fixed a null reference error occasionally caused by double-clicking in
dgvActions
without actually selecting an action; - Fixed an error where disabled actions were not ignored when calculating total delay in the descriptions;
- Fixed the issue where undo button would mess up
OrderNumber
. Also changed shallow copy to deep copy when saving the actions, allowing to undo modifications of individual actions; - Added quick settings in the action right-click menu for batch adjustment of async, delay, conditions, and other action properties;
- After pasting actions, the pasted actions are selected;
- Deselect actions when clicking elsewhere;
- Fixed a bug where some buttons in
-
ConditionViewer
- Added options to expand/collapse all in the conditions right-click menu;
- Expanded all conditions on load.
(https://discord.com/channels/374517624228544512/1154813334835707957)
-
ExpressionTextBox
- MaxLength changed from default
32767
to1000000
for scripting usage; - Corrected the parameter name error in the autocomplete Table method
vlookup()
; - Fixed a calculation error in
MaximumSize
and adjusted the height;
- MaxLength changed from default
-
ActionForm
- Added built-in text hints for variable operations and some of the other actions, integrating text hints for some actions like
SendKeys
; - Fixed a bug in the
KeyPress
action test: the text box says it would delay two seconds before execution, but actually there were no code for this delay; - Fixed the wrong output results of the keyboard recording tool in
SendKeys
action.
(https://discord.com/channels/374517624228544512/599935468578144276/1152402243245588572)
- Added built-in text hints for variable operations and some of the other actions, integrating text hints for some actions like
-
LogForm
- When all levels of log types are checked,
Select All
is automatically checked;
- When all levels of log types are checked,
-
StateForm
- Adjusted the column widths;
- Fixed an issue that the named callback interface was not working, as the RefreshNamedCallbacks function originally had no content;
-
BridgeFFXIV
- Fixed the error where
NullCombatant
was defined but never initialized; - Changed attributes in
ClearCombatant
to empty strings to avoid confusion, likeNullCombatant.x
being0
might be mistaken for querying an entity withx = 0
; - Added all properties related to
Jobs
to the entity dictionary; - Replaced
TranslateJob
,TranslateRole
functions with a dictionary defined inEntity.cs
;
- Fixed the error where
-
Action
- Fixed the issue where
KeyPress
,SendWindowMessage
action descriptions did not supportprocid
; - Added dictionary actions
GetEntityByName
/GetEntityById
, table actionGetAllEntities
, to obtain single or all entity information at once; - Table action
Resize
can omit one of width/height to indicate that dimension remains unchanged;
- Fixed the issue where
-
Context
- Fixed the error of using greedy matching for variable names in some of the regexes;
- Added two dynamic expressions
_rowcl[...]
_colrl[...]
to return the cell value by its corresponding header (liketvarrl
tvarcl
); - Added
ecallback:...
to check for the existence of a named callback; - Fixed the error of placing table
contain
andifcontain
methods in the dictionary variable code block; - Changed
func:pick():string
to split string based on the same logic as splitting parameters, instead of simply splitting by comma.
-
Interpreter
- Error messages specify the exact API name when API is restricted;
- During script execution, catches
AggregateException
and generates corresponding error messages; - Added
SetDictVariable
andGetDictVariable
; - For
SetXXXVariable
, deletes the variable if the provided variable parameter is given asnull
;
-
MathParser
- Added operator
??
: If the previous parameter cannot be parsed as a number, then returns the next parameter, similar to the null coalescing operator.
e.g.,1 ?? 2 = 1
,A ?? 2 = 2
; - Fixed an error of the value range of the
roundir
/roundvec
functions; - Added direct input support for hexadecimal, binary, octal, like
0xFF
,0b11
,0o77
; - Added condition checks for situations where a
${...}
expression returns an empty string causing==
,??
to be at the start or end of tokens list
- Added operator
-
RealPlugin
- Verified the content of the last line of the temp file when saving configurations, attempting to fix issues of incomplete file ends caused by abrupt ACT exit during
StreamWriter.write
; - When loading the configuration file, check the last line to judge if the file is corrupted. If so, the
.previous
backup file will be automatically loaded. - Fixed a rare null reference exception in
ReadyForOperation
function; - Added overloaded
RegisterNamedCallback
andUnregisterNamedCallback
functions for adding/removing callbacks by name in scripts.
- Verified the content of the last line of the temp file when saving configurations, attempting to fix issues of incomplete file ends caused by abrupt ACT exit during
The core of the MathParser had been mainly rewritten:
Several bugs in the parsing logic for minus signs have been fixed:
- The original code only checked the preceding character to determine if a
+
/-
was a sign or an operator. This oversight caused incorrect parsing of expressions likefunc(1, -1)
or1 - -1
.- Related: #87
- In the function
BasicArithmeticalExpression()
, only positive values were handled when applying a minus sign, neglecting non-positive values. This led to incorrect parsing of expressions like-(-1)
into-1
.- Related: Discord
- The original code gave the minus sign a hidden highest priority over other operations, leading to errors in the calculation order, such as
-2^2 = 4
(whereas the answer should be -4 in most modern languages). - The original code failed to recognize the
-
in-func(args)
as a minus sign, causing errors in expressions like0 = -sin(0)
. The code would attempt to apply a minus operation between=
andsin(0)
.- Related: Discord
These bugs have been fixed, and the entire logic for handling +/- signs has been rewritten for simplicity. Previously, special logic was used to treat +
/-
in both the lexer and the parser. Now, the lexer tokenizes every +
/-
without discrimination, leaving simple logic in the parser to handle them correctly.
- The lexer has been simplified to focus only on whether the current character is part of an operator. It then separates operators from non-operator characters.
- This logic passes the invalid inputs to the parser, and the parser now can raise errors detailing which operator in which expression could not be parsed.
- The earlier lexer tried to add
*
between expressions like3abs(0)
, but this approach is not correct both in logic and in code:- Distinguishing between
3ab
in3abs(0)
and the hex number3ab
is not possible without scanning the entire list of functions to match names; - The code would never give it a chance to consider
a
as the first character of the token to apply this logic, when there is a3
in front of it.
- Distinguishing between
- Since it needs excess time for scanning the whole list of functions to match the expression, this is not changed and an explicit
*
between numbers and functions/constants is still required.
- The parser now supports unary, ternary, and right-associative operators, in addition to the previously supported single-character left-associative binary operators.
- The following operators have been added:
&&
||
^^
!
: logical AND / OR / XOR / NOT%%
//
: realmod
and exact division√
: sqrt()>=
<=
!=
as aliases==
the only string operator: string equal to.DD5 == DD5
=1
.&
|
⊕
~
<<
>>
: bitwise operations (useful for representing the state of multiple entities appearing in random locations using a single variable)? :
: ternary operator
- All operators in order:
√
!
~
^
%
%%
/
//
*
-
+
<<
>>
>
≥
>=
<
≤
<=
==
=
≠
!=
&
⊕
|
&&
^^
||
?
:
(
)
,
(not parsed directly)
- The tolerance level has been changed from
double.Epsilon
to a constant (set toδ
=1E-9
), making it more suitable for Triggernometry use. - This rule only applies to functions and operators involving (explicit or implicit) comparisons, like
=
>
>=
sign
truncate
, etc. - This rule will not reduce any computational accuracy.
- Values within the range
x ± δ
will be considered equal tox
during comparisons.
- In the original code, a list of aliases (e.g.,
atan2
=>arctan2
) existed but was not utilized. - Those lines of code have been removed due to being irrelavent with Triggernometry requirements, and the aliases have been directly incorporated into the list of functions.
- Spaces are now stripped from the expression before parsing to simplify the logic.
- Side effect: Numerical string functions can no longer include meaningful spaces within their arguments (though this type of function was not previously supported).
-
The original
SplitArgument
function generated incorrectargs
lists when encountering unmatched quotes or consecutive commas, and it also did not support line breaks. -
The edited code now employs regular expressions to precisely extract all parameters situated between the string's beginning, end, and commas.
-
Unquoted expressions are trimmed; empty or unquoted whitespaces return an empty list.
-
e.g. (1,2, 3 ," 4 ", "5" , "'", ', ', ) now translates to a list with the arguments:
-
1
2
3
4
5
'
,
(empty)
. -
Arguments should not contain
)
{
}
due to the parsing logic in expanding expressions, so the following escape rules are applied:{
should be escaped with full-width{
or__LB__
;}
should be escaped with full-width}
or__RB__
;(
can be escaped with full-width(
or__LP__
;)
should be escaped with full-width)
or__RP__
;{
}
should be escaped with__FLB__
__FRB__
;(
)
should be escaped with__FLP__
__FRP__
;
- Indices now support negative values, counting backwards.
- This includes:
substring
;lvar
;tvar
;tvarrl
;tvarcl
; and theindex
/row
/column
text boxes in list/table actions. - The previous keyword "last" now redirects to -1.
- e.g.
${tvar:myTable[2][-3]}
returns the value in the2nd
column and the-3rd
row.
- A slice expression like
start:end:step
can represent a series of indices starting fromstart
, incrementing bystep
each time, and ending beforeend
. - This functionality closely resembles Python's slicing mechanism.
- All three integers can be omitted:
a:b:c
a:b:
a::c
:b:c
a::
:b:
::c
::
a:b
a:
:b
:
- Indices support negative values; strings count from 0 and lists/tables count from 1, maintaining consistency with previous Triggernometry features.
- Slices (and indices) can be combined into a slices expression, such as:
":5, 7, 8:13:2, 16:"
represents(0), 1, 2, 3, 4, 7, 8, 10, 12, 16, 17, (...to the end)
. - Slice expressions are now commonly used in new features to combine multiple functions, enable "fake" higher-dimensional list/table operations in string expressions, and also to simplify expressions/actions without using loops.
- Note: "slices" is considered a single argument in functions and methods. If it contains a comma, enclose the slices expression in
""
or''
.
- Fixed a bug where autofill sometimes remained visible, such as after entering
tvar:
and still showingtvarrl
; - Added more types of autofill: list/table/dict methods; current variable/aura/const names; regex capture groups in the current trigger; dict keys and table lookup headers;
- The code now searches for the preceding unclosed
{
to provide smarter autofill suggestions; (e.g.,${lvar:xxx${var:yyy}${index}.
will display list properties); - The autofill box now appears directly below the matched strings;
- Fixed a bug where using the enter key to select an autofill suggestion in multi-line mode also inserted a line break;
- Increased the height (5 → 10) and width (→ max length of suggestions) of the autocomplete box;
- Added a debounce timer: a 200 ms countdown starts after text changes in the textbox and triggers the autofill logic. Any changes within this countdown reset the timer, preventing lag when typing quickly or holding backspace.
- Finalized the definition of the previously unused VariableDictionary type and added corresponding expressions, methods, and actions.
- See below for details.
Expression | Description |
---|---|
_ETprecise |
Provides the exact minutes in the Eorzean day. A more accurate version of _ffxivtime (_ET ). |
_idx |
Dynamic expression. Represents the current index. |
_col |
Dynamic expression. Represents the current column index. |
_row |
Dynamic expression. Represents the current row index. |
_col[i] |
Dynamic expression. Represents the value of the row index i in the current column. |
_row[i] |
Dynamic expression. Represents the value of the column index i in the current row. |
_this |
Dynamic expression. Represents the value in the current grid. |
_key _val |
Dynamic expression. Represents the current key/value. |
_clipboard |
Current copied text in the system clipboard. |
_config[x] |
Returns some user configurations that would impact the results of user operations or triggers. (See Autofill form for details) |
For details on dynamic expressions, refer to the actions section.
Expression | Description |
---|---|
semitone |
2^(1/12) . The frequency ratio between 2 adjacent semitones. |
cent |
2^(1/1200) . The frequency ratio between 2 adjacent cents. |
ETmin2sec |
35/12 . The ratio between 1 ET minute and 1 real second. |
δ |
1E-9 . The math tolerance for comparison. (check the previous part) |
Expression | Description | Examples |
---|---|---|
parsedmg(hex) |
Converts the given hex string for damage/healing in the 0x15 /0x16 ACT log lines to the corresponding decimal value.Rule: Padleft the hex string with 0 s to 8 digits as XXXXYYZZ , then convert ZZXXXX to decimal. |
parsedmg(A00000) = 160 |
freq(note, semitones = 0) |
Returns the frequency (Hz) of the specified note (using scientific pitch notation, accidentals represented as # , b , x ) adjusted by the semitones offset. |
freq(A4) = freq(G#4, 1) = freq(Bb4, -1) = freq(A5, -12) = 440 |
nextETms(XX:XX) nextETms(ETminutes) |
Provides the time (ms) remaining until the next specified Eorzean time. | The current ET is 1:00 : nextETms(2:00) = nextETms(02:00.00) = nextETms(120) = 175000 (2 min 55 s) |
Expression | Description | Examples |
---|---|---|
parsedmg |
Same as the numeric function. No arguments accepted. | func:parsedmg:A00000 = 160 |
slice(slices = ":") |
Accepts a "slices expression" as an argument. Returns the specified slice of the string. |
func:slice(-3):01234 = 2 func:slice(1:4):01234 = 123 func:slice(::-1):01234 = 43210 func:slice("1,3:"):01234 = 134 |
pick(index, separator = ",") |
Separates the given string by the specified separator. Returns a substring based on the index, starting from 0. Also supports negative indices. |
func:pick(3):north,west,south,east = east func:pick(-1,", "):1, 22, 3, 44, 5 = 5 |
contain(str) startwith(str) endwith(str) equal(str) |
Returns 1 or 0 . |
func:contain(23):1234 = 1 func:endwith(23):1234 = 0 func:equal(23):1234 = 0 |
ifcontain(str, t, f) ifstartwith(str, t, f) ifendwith(str, t, f) ifequal(str, t, f) |
Similar to if() . |
func:ifcontain(23, a, b):1234 = a func:ifendwith(23, a, b):1234 = b func:ifequal(23, a, b):1234 = b |
indicesof(str, joiner = ",", slices = ":") |
Searches for all indices in the specified slices of the string, then joins them. | func:indicesof(a):abcbabcba = 0,4,8 func:indicesof(a, "-", ::-1):abcbabcba = 8-4-0 |
match(str):regex |
Returns 1 or 0 .Note: regex should not contain { } .{ should be escaped with full-width { or __LB__ ;} should be escaped with full-width } or __RB__ ;{ } must be escaped with __FLB__ __FRB__ .Same regex rules apply to the next two functions. |
func:match(404D):404[B-D] = 1 func:match(4000A3BF):4.{7} = 1 |
capture(str, group):regex |
Returns the captured string $groupindex or ${groupname} . If groupindex = 0 , it returns the entire matched string. If groupname isn't found or groupindex is out of range, it returns an empty string.Adheres to the previously mentioned regex rules. |
func:capture(Player NameGilgamesh, server):.+ .+(?<server>[A-Z].+) = Gilgamesh |
ifmatch(str, t, f):regex |
Similar to if() . |
func:ifmatch(404D, a, b):404[B-D] = a |
replace(oldStr, newStr = "", isLooped = false) |
Replaces one string with another in the specified string. | func:replace(" "):1 2 3 = 123 func:replace(aa,a):aaaaaa = aaa func:replace(aa,a,true):aaaaaa = a |
repeat(times, joiner = "") |
Repeats the string the specified number of times. | func:repeat(3):a = aaa func:repeat(3, +):1 = 1+1+1 |
padleft padright trim trimleft trimright |
These functions now accept character arguments either as the character itself or its charcode. 0 -9 are interpreted as characters rather than charcodes since ASCII 0-9 control characters are rarely used here. Numbers ≥ 10 are seen as charcodes. No need for the 5-digit charcodes of CJK-region characters (including full-width spaces). |
func:trim(48, 2, a):abcd0320 = bcd03 func:padleft(0,8):1ABCD = 0001ABCD |
- This section uses
lvar:test
=1, 2, 3, 4, 5, 6, 7, 8, 9
(this string is only a representation of the list) for demonstration.
Expression | Description | Examples |
---|---|---|
${?lvar:...} |
Builds a temporary list directly from the expression split by , , and uses any properties on it to return a string.Uses the same splitting rule as for splitting arguments. Building a variable might be slightly slower, but it provides a way to combine multiple actions with conditions into a single action. |
${?lvar:a, b, c, d, e.indexof(c)} = 3 ${?lvar:a, b, "c,c", d, e[3]} = c,c |
sum(slices = ":") |
Returns the sum of the values in (the slices of) the list. Only values that can be parsed into the double format are summed. |
${lvar:test.sum} = 45 ${lvar:test.sum(1:5)} = 10 |
count(str, slices = ":") |
Returns the number of times the string appears in (the slices of) the list. | ${lvar:test.count(3)} = 1 ${lvar:test.count(a)} = 0 |
join(joiner = ",", slices = ":") |
Joins (the slices of) the list using the specified joiner. | ${lvar:test.join} = 1,2,3,4,5,6,7,8,9 ${lvar:test.join(" ",5::-1)} = 5 4 3 2 1 |
randjoin(joiner = ",", slices = ":") |
Similar to join(), but the selected elements are shuffled before being joined.. | ${lvar:test.randjoin} = 4,9,2,3,5,7,8,1,6 (random example) |
contain(str, slices = ":") |
Returns 1 if (the slices of) the list contains the given string, otherwise 0. | ...contain(3) = 1 ...contain(3, 4:) = 0 |
ifcontain(str, trueExpe, falseExpr) |
Similar to if() . If the list contains the string, returns trueExpr ; otherwise, returns falseExpr . |
...ifcontain(3, found, missing) = found ...ifcontain(a, found, missing) = missing |
indicesof(str, joiner = ",", slices = ":") |
Searches for all occurrences of the string in the given slices of the list and joins the indices into a string. Similar to the string function. | |
max(type = "n", slices = ":") min(type = "n", slices = ":") |
Returns the extremum value in (the slices of) the list, depending on the type: n for numeric, h for hex, s for string. |
${lvar:test.max} = 9 ...min(n, 3:) = 3 |
- This section demonstrates using the following
tvar:test
:
11 | 21 | 31 | 41 |
---|---|---|---|
12 | 22 | 32 | 42 |
13 | 23 | 33 | 43 |
14 | 24 | 34 | 44 |
Expression | Description | Examples |
---|---|---|
${?tvar:...} |
Builds a temporary table directly from the expression split by , and | .Similar to ${?lvar:} . |
`${?tvar: a,b |
tvardl: ptvardl: |
Double-based lookup similar to tvarrl: /tvarcl: . Returns the value identified by the column and row headers. |
${tvardl:test[41][13]} = 43 |
sum(colSlices = ":", rowSlices = ":") |
Returns the sum of the values in (the sliced rows and columns of) the table. Only values that can be parsed into the double format are summed. |
${lvar:test.sum} = 440 ...sum(1, :) = 11 + 12 + 13 + 14 = 50 |
count(str, colSlices = ":", rowSlices = ":") |
Returns the number of times the string appears in (the sliced rows and columns of) the table. | ${lvar:test.count(33)} = 1 ${lvar:test.count(1)} = 0 |
hjoin(joiner1 = ",", joiner2 = "⏎", colSlices = ":", rowSlices = ":") |
Horizontally joins the table using the specified joiners. | ...hjoin(",", ",", 1:3, 3:) = 13,23,14,24 ${tvar:test.hjoin} = 11,21,31,41 12,22,32,42 13,23,33,43 14,24,34,44 |
vjoin(joiner1 = ",", joiner2 = "⏎", colSlices = ":", rowSlices = ":") |
Vertically joins the table using the specified joiners. | ${tvar:test.vjoin} = 11,12,13,14 21,22,23,24 31,32,33,34 41,42,43,44 |
hlookup(str, rowIndex, colSlices = ":") |
Searches for the string in the given row and returns the column index. If not found, returns 0. |
...hlookup(13,3) = 1 ...hlookup(13,3,2:) = 0 |
vlookup(str, colIndex, rowSlices = ":") |
Searches for the string in the given column and returns the row index. If not found, returns 0. |
...vlookup(13,1) = 3 |
max(type = "n", colSlices = ":", rowSlices = ":") min(type = "n", colSlices = ":", rowSlices = ":") |
Same as the list method. | (omitted) |
- This part uses
dvar:test
=a=1, b=2, c=3, d=3, e=3
(this string is only a representation of the dictionary) to demonstrate:
Expression | Description | Examples |
---|---|---|
${?dvar:...} |
Builds a temporary dict directly from the expression split by = , . .Similar to ${?lvar:} . |
${?dvar: 7CD2=in, 7CD6=out, 7CD7=spread [7CD2] } = in |
sumkeys() sum() |
Sum all the keys/values that can be parsed into double format. |
${dvar:test.sumkey} = 0 ${dvar:test.sum} = 12 |
count(value) |
Returns the count of the given value in the dict. | ...countvalue(3) = 3 |
dvar: edvar: pdvar: epdvar: |
e (existing) / p (persist). Similar to other variables. | ${epdvar:dictname} ${dvar:test[e]} = 3 |
length / size |
The number of keys in the dict. | ${dvar:test.size} = 5 |
ekey(key) evalue(value) |
Check if the key/value exists in the dict (returns 0/1). | ${dvar:test.ekey(a)} = 1 ${dvar:test.evalue(4)} = 0 |
ifekey(key, t, f) ifevalue(value, t, f) |
Similar to string functions (returns string t/f). | ...ifekey(a, found, missing) = found |
keyof(value) |
Reverse lookup by value. Returns the first found key or an empty string if not found. | ${dvar:test.keyof(1)} = a ${dvar:test.keyof(4)} = `` |
keysof(value, joiner = ",") |
Lookup all keys matching the given value and join them with the joiner. | ...keyof(3) = c,d,e |
joinkeys(joiner = ",") joinvalues(joiner = ",") joinall(kvjoiner = "=", pairjoiner = ",") |
Combine the keys/values/both using the joiners. | ...joinkeys(-) = a-b-c-d-e ...joinall = a=1,b=2,c=3,d=3,e=3 |
max(type = "n") min(type = "n") maxkey(type = "n") minkey(type = "n") |
Same as the list methods. | (omitted) |
${_job[XXX].prop}
: returns the property of the specified job.- Properties:
- role; job; jobid (same as _ffxiventity)
- isT; isH; isTH; isD; isM; isR; isTM; isHR; isC; isG; isCG; (0 or 1)
- jobCN; jobDE; jobEN; jobFR; jobJP; jobKR; (full names in different languages)
- jobCN1; jobCN2; jobEN3 (= job); jobJP1 (abbreviations of varying lengths)
jobXX
,jobXXn
,jobid
could be used as the keyXXX
in${_job[XXX].prop}
.- These properties are also included in
_ffxiventity
and_ffxivparty
. - e.g.
${_job[Gladiator].jobid}
=1
;${_job[1].jobFR}
=Gladiateur
;${_job[GLA].jobCN1}
=剑
;${_ffxiventity[Gladiator Player].isTM}
=1
- Several entity properties have been added to
${_ffxiventity}
and${_ffxivparty}
:bnpcid
,bnpcnameid
,ownerid
,type
,partytype
,address
castid
,casttime
,maxcasttime
,iscasting
- Expressions can become extremely long when working with complex logic, which may involve several nested
${}
instances. - To alleviate this, several abbreviations have been introduced to shorten these expressions:
Full | Abbrev. |
---|---|
${numeric:...} |
${n:...} |
${string:...} |
${s:...} |
${func:...} |
${f:...} |
${exvar:...} |
${ev:} ${el:} ${et:} ${ed:} |
${(p)var:...} ${(p)lvar:...} ${(p)tvar:...} ${(p)dvar:...} |
${(p)v:} ${(p)l:} ${(p)t:} ${(p)d:} |
${?lvar:...} ${?tvar:...} ${?dvar:...} |
${?l:} ${?t:} ${?d:} |
${_loopiterator} |
${_i} |
${_ffxiventity[...].prop} |
${_entity[...].prop} |
${_ffxivparty[...].prop} |
${_party[...].prop} |
${_ffxivplayer} |
${_me} |
${_ffxiventity[${_ffxivplayer}].prop} |
${_me.prop} |
indexof() |
i() |
_ffxivtime |
_ET |
pi |
π |
distance() |
d() |
projectdistance() |
projd() |
projectheight() |
projh() |
angle() |
θ() |
relangle() |
relθ() |
(table) .width |
.w |
(table) .height |
.h |
(table) .hlookup() |
.hl() |
(table) .vlookup() |
.vl() |
(entity) .heading |
.h |
- The original code for the list variable inserted
null
directly as placeholders when the given index exceeded the length of the list. It should have used a newVariableScalar
instead. - This led to two issues: the parser was unable to retrieve these null values in expressions, and the list couldn't be double-clicked to view in the variable viewer due to an ACT error.
- Similarly, the code for table variables appended empty rows, setting each grid to
null
:vtr.Values.AddRange(new Variable[newWidth]);
Rows[i].Values.AddRange(new Variable[newWidth - Rows[i].Values.Count]);
- The original code inserted one fewer
VariableScalar
into the list as placeholders when the list needs to be expanded. - This caused a problem when trying to set a value at a given
index
in a list with lengthindex - 1
. The result was an appended list that lacked the value to be set.
- The original code did not adhere to the persistent options of the source/target variables.
- It is not enabled but the option is still effective when unsetting all variables.
- Enabled the persistent button when selecting these actions.
- List:
_this
_idx
- Table:
_this
_row
col
_row[i]
col[i]
- Dictionary:
_val
PopFirst
was modified to accept an optionalindex
argument, which also supports negative values.- The action name
PopFirst
in the code remained unchanged, andPopLast
was retained for XML compatibility reasons. - However, the functionality of
PopLast
now redirects toPopFirst
with index =-1
.
- Pop and insert
- Pops an element from the source list and inserts it into the target list.
- If the target index was not given, it would be appended to the end of the list.
- Note: this is not the same as index
-1
, which inserts the value between the last 2 elements - e.g. source list:
1,2,3,4,5
, source index:3
, target list:a,b,c,d,e
- target index:
3
=>a,b,3,c,d,e
; - target index:
-1
=>a,b,c,d,3,e
; - target index:
a,b,c,d,e,3
;
- target index:
- Pop and set
- Pops a element and set it to the given index in the target list.
- Both of the indices should be given.
- e.g. other conditions same as above;
- target index:
3
=>a,b,3,d,e
; - target index:
-1
=>a,b,c,d,3
;
- target index:
- These actions are just combinations of
Pop
andSet
/Insert
. - The
expression
textbox is used as the target index input, and the descriptions and label instructions would be changed automaticlly.
Unset
,UnsetAll
,UnsetRegex
Set
: Assigns a value to a key.Remove
: Removes a specified key if it exists.Merge
: Combines dictionaries but leaves repeated keys unaltered.MergeHard
: Combines dictionaries and overwrites any repeated keys.
- Build a list variable by using the first character of the expression as the separator, with the remainder of the expression as the input string. For table variables, the first two characters serve as the column/row separators. For dict variables, the first two characters act as the key-value pair separator and the overall pairs separator.
- This allows for the easy creation of lists/tables directly from a given string with a single action.
- Further, one can generate a list from a slice of another list or a row/column of a table in a single step when combined with
list.join
,table.hjoin
, ortable.vjoin
. - e.g. The expressions
,1,2,3,4,5,6,7,8,9
and,|11,21,31,41|12,22,32,42|13,23,33,43|14,24,34,44
can build the previouslvar:test
andtvar:test
;
=,a=1,b=2,c=3
can build the dictionarya=1, b=2, c=3
.
- Provides a flexible method to assign values to a list/table/dict variable, similar to the
Select()
in LINQ expressions. - The dynamic expressions below can be utilized when traversing the entire variable:
- List variables:
${_this}
${_idx}
- Dict variables:
${_idx}
(when a dict length is specified) or${_key}
${_val}
- List variables:
- For table variables:
${_this}
,${_row}
,${_col}
can be used. - e.g.
- Using the SetAll action with the expression
${_idx}
on a list (length = 9) produceslvar:test
with values (1-9); - Then, using SetAll with the expression
${_this}^2
onlvar:test
results in1, 4, 9, ..., 81
. - For a dictionary of length
5
, using key expression${_idx}
and value expression${_idx}^2
produces a dictionary1:1, 2:4, 3:9, 4:16, 5:25
. - Applying the key expression
${_value}
and value expression${_key}
reverses it to1:1, 4:2, 9:3, 16:4, 25:5
.
- Using the SetAll action with the expression
- This is similar to the
Where()
function in LINQ expressions. - Filters elements of a list to a new list, grids of a table to a new list, rows/columns of a table to a new table, or key-value pairs of a dict to a new dict, based on whether the dynamic expression evaluates to true (
!= 0
). - For example, the expression
${func:ifequal("", 0, 1):${_this}}
eliminates empty elements; ${lvar:listname.indexof(${_this})} = ${_idx}
removes duplicate elements in the list namedlistname
;- Using the expression
${_ffxiventity[${_this}].distance} > 15 && ${func:ifequal(DPS):${_ffxiventity[${_this}].role}}
on a list containing player names filters out DPSs located more than 15 m away from the player.
- Similar to the
Build
action for lists, it splits the expression into a list based on its first character. Depending on the provided index (row/col), the list of values is then set/inserted into the specified row/col. This counting - - logic is similar to that of theSet
/Insert
actions for list variables. - e.g. Using the SetLine action with a row index of
3
and the expression,a,b,c,d,e
on the priorlvar:test
yields:
|11|21|31|41||
|---|---|---|---|---|
|12|22|32|42||
|a|b|c|d|e|
|14|24|34|44||
- This action removes a specified row/column from a table.
- For instance, removing row index
3
from the previouslvar:test
results in:
|11|21|31|41|
|---|---|---|---|
|12|22|32|42|
|14|24|34|44|
- These actions sort based on the specified key functions, proving useful for scenarios involving multi-criteria sorting, such as in TOP dynamis.
- Key functions format:
n+:key1, n-:key2, s+:key3, ...
n
/s
: numeric/string comparison+
/-
: ascending/descending (the+
is optional)key
: should include${_this}
/${_idx}
for lists,${_row}
or${_row[i]}
for row sorting,${_col}
or${_col[i]}
for column sorting.
- If an function contains commas, or starts/ends with spaces, it should be enclosed in quotes, like
"s+:key", ...
or's+:key', ...
. e.g.n+:${_this}
n-:${_this}
s+:${_this}
s-:${_this}
correspond to the previous four sorting actions.- Sorting by
n-:${_idx}
reverses the list. - Sorting the list
[11, 12, 13, 21, 22, 23, 31, 32, 33]
with the functionsn-:${f:substring(0):${_this}}, n+:${_idx}%3
produces[33, 31, 32, 23, 21, 22, 13, 11, 12]
.
- This will unset the scalar, list, table, dictionary variables in one step.
- The persistant option is still respected: unsetting the session vars would not affect persistent vars.
- Useful when initiating a raid phase.
- If the variable name is provided, its value will be copied directly to the clipboard without any parsing.
- If only an expression is provided, it will be interpreted as a string expression and then copied to the clipboard.
- Note: Actually, this doesn't necessarily involve scalar variables. To set a scalar variable to the clipboard, you can simply input
${var:name}
in the expression (unless your clipboard contains${...}
expressions). I have organized it this way just to logically group this clipboard operation under the scalar variable category, avoiding the creation of a separate tabpage which could further slow down the action form loading.
- Related issue: #48
- Reorganized the actions, list actions, and table actions into a more logical sequence.
- Additionally, replaced several opTypes that were hardcoded as integers with their corresponding enums.
- Only allow the selection between textboxes when switching with Tab.
- Also corrected some orders.
- Add a
Color
ExpressionType enum to let the textbox show the input as its background color; - Change the textbox bgcolor to light blue if the related persistent switch is on.
- Add a light yellow warning color when the numeric/string expression textbox contains an alphanumeric
${...}
expression that is not a capture group or a special variable (like_since
). - In multiline mode, change from a fixed height of 100 to a height limit of 100-300, dynamically adjusting according to the text height.
- Added arguments to represent if the variable is persistent and if the expression is numeric/string in the action descriptions (and also log messages);
- Added a
[Sync]
prefix to the description if the async option of an action is unchecked; - Added a warning color when an action has a non-zero delay and the description text is overridden (this usually happens as a mistake when copying and editing actions, and it is hard to debug);
- Added color options in the action description page to allow customized bg/text colors in the descriptions; (format:
Lavender
/230,230,250
/#e6e6fa
/#eef
) - Added the buttons
Move to top
andMove to bottom
, and enabled the moving of multiple selected actions; - Added the button
Undo
to enable the undo of movement / delete of actions for one step; Add action
now insert the action under the selected line instead of set it to the bottom;Save changes
button would change intoSave and Fire
if autofire is enabled;- Added a trigger description label on the bottom showing some info about conditions, source, refire options, sequential, etc;
- Added a message box to confirm quiting without saving the trigger;
- Deselect the action rows when clicking elsewhere.
- Added more precise error information in the logs like which expression caused error when expanding expressions;
- Added
custom
log types (info
<custom2
<custom
<warning
) which should contain no log from the program. - Adjusted the
warning
/error
log message colors to be less saturated instead of pure red/yellow, and also added a green color to thecustom
logs. - Use checkboxes instead of comboboxes to filter logs (left-click toggles selection, right-click selects exclusively), which is much clearer:
- Added support for dictionary variables;
- Added support for sorting the 8 types of variables by clicking their corresponding dgv table headers;
- Let the selected grid move to the next column / row after adding a column / row. (not changed for inserting);
- Fixed the bug that some columns could not be adjusted in variable state viewer: some columns were set to
Fill
, which forbids the adjustment of its column width if it is not the last column.
- Enabled the
Add trigger / folder
buttons when a local trigger is selected, which adds the trigger / folder to the parent folder, just like pasting triggers from xml; - Similarly, enabled drag and move onto another trigger, which would moved the selected item into the parent folder;
- Automatically select the trigger/folder after drag and move;
- Added shortcuts:
-
Func Without Parameters: Fixed an issue where string functions without arguments weren't parsed correctly.
- Related Issue: #92
- The original regex misinterpreted expressions like
func:length:3*(1+2)
, which considerslength:3*
as the function name and1+2
as the argument. - Modifications to the regex now allow it to correctly match the entire expression in a single step, instead of looking for the
:
later. - The regular expressions also underwent minor adjustments.
-
Hi-Res Action Checkboxes: Rectified a bug causing action checkboxes to remain hidden on Hi-Resolution screens.
- Related Issue: #91
-
MessageBox Display: Addressed an issue causing the MessageBox to sometimes hide behind active windows. Now, MessageBoxes will always appear above the currently active window.
-
Linebreaks in Expressions:
- Improved support for linebreaks which previously conflicted with argument splitting, code trimming, and regular expressions.
- A special character
⏎
was introduced to act as a placeholder for linebreaks during parsing, which is replaced post-parse. - This character can also be used directly in Triggernometry expressions, for instance,
${func:repeat(5, ⏎):text}
.
-
Translations:
- Updated and revised several hundereds of missing translations since version 1.1.6.0.
- Updates have been applied to both CN/JP translation files. (Note: The FI/FR files were notably outdated, with jumbled orderings.)
- Fully revised CN translations.
-
Trigger Firing Settings:
- Previously the triggers would ignore all contidion checks when manually fired, but sometimes we want it to respect all conditions.
- Enhanced manual trigger firing to optionally respect conditions through the
Fire (Allow Conditions)
right-click menu option. - Also, introduced an
Allow conditions for autofiring
setting for triggers.
-
Test Action: Introduced a
Test action with live values (ignore conditions)
option and a corresponding default configuration setting to bypass conditions during tests. -
CSV Export: Enhanced support for table variables that contain commas and double quotes, providing more accurate exports, instead of simply joining together with
,
. -
Miscellaneous Adjustments:
- Optimized some repeated codes;
- Added
CultureInfo.InvariantCulture
to some of theParse()
andToString()
functions that missed localization settings. - Corrected typos.
- etc.
Besides the bug fuxes and UI adjustments, the following behaviors are different comparing with the old version:
-
Mathparser Adjustments:
- The
:
character is now part of the? :
ternary operator. - The
^
operator is now right-associative. - The precision error tolerance is set to
1E-9
(i.e.,0.1234567890 = 0.1234567899
is considered true). - Check the previous section for details.
- The
-
Input Validation: Some of the undefined or invalid inputs, which previously returned default values `` or
0
, now raise specific error messages. -
Beep Frequency: The default beep frequency is not out-of-tune anymore (C6, 1046.5 Hz).
- Dynamic Variables: Variables like
${_idx}
aren't thread-safe and should be used exclusively with synchronous actions. - Math Parser Limitations: Doesn't support operators sharing the same priority level and not fixed in this version.
- Dictionary Editor Display: Dictionary variables now list keys in their intrinsic order instead of a sorted manner.
- Performance: Loading the action form has been notably slow (~0.8-2 s) from long time ago, possibly due to the expanded variety of actions. I am not familiar with WinForms and I am wondering if it could be fixed.
- Autofill: Occasionally the autofilled regex groups are not updated after the trigger regex has been edited. Could be fixed by clicking the regex textbox.
- An error was once reported upon clicking the action, but it could not be reproduced.
System.ArgumentOutOfRangeException - Index out of range.
in System.Collections.ArrayList.get_Item(Int32 index)
in System.Windows.Forms.DataGridViewSelectedRowCollection.get_Item(Int32 index)
in Triggernometry.CustomControls.ActionViewer.btnEditAction_Click(Object sender, EventArgs e)
in Triggernometry.CustomControls.ActionViewer.dgvActions_CellDoubleClick(Object sender, DataGridViewCellEventArgs e)
in System.Windows.Forms.DataGridView.OnCellDoubleClick(DataGridViewCellEventArgs e)
in System.Windows.Forms.DataGridView.OnDoubleClick(EventArgs e)
- Detailed action descriptions
- review
- Show the count of errors
- combobox row height (written but not used: the customized drawing caused the loading time changed from 1 s to 3 s)
- ReadMemory
- More entity data from memory
- Global font settings
- Aura font size
- More intelligent autofill: close the brackets, move the cursor, and refresh the autofill form.
- RichTextBox
- Dictionary Variable Editor (Sort)
- Vector and matrix
- Add customized exceptions to the math functions (need to tolerate with testmode and the exptextbox bgcolor logic).
-
_color[x][y]
- deselect actions in loop dgv