Skip to content

Leviathon Language Reference

AsteriskAmpersand edited this page Jan 15, 2023 · 31 revisions

Project Logo The following is a terse description of the Leviathon language. This is not intended to be read as prose but act as encyclopedic entries on the different data types as seen by the compiler.

The Leviathon language has a reference compiler, the Amsterdam-Bucharest Compiler which can be downloaded from here. The project page can be found here.

The language was first proposed by AsteriskAmpersand as a way of realizing an earlier proposal by Stracker to define a compillable higher level language for monster THKs. The language syntax and semantics were agreed upon by the Leviathon Language Design Committee which included NackDN, Fandirus, Silvris, Fexty and AsteriskAmpersand. The compiler-decompiler was written by AsteriskAmpersand who had help in verification and testing from Fandirus.

NACK Files

Assignment

Asignments consist of a pair of Local Identifier and Numeric Literal joined by an equal sign =. The compiler will resolve all instances of the local identifier to the numeric liteeral on the right side.

Import

Imports consist of two parts. A resource identifier and an alias. The alias must be a Local Identifier. The resource identifier can either be an enquoted windows path (either relative or absolute) or a THK Identifier declared in the FAND File.

Library Import

Library imports allow accessing the local identifiers in a separate source file, this includes both assignment and node names.

Internal Library Import

Internal Library Imports correspond to imports where the resource identifier is a THK Identifiers declared in the FAND File. This is resolved to the path bound to the THK Identifier, with a metadata tag related to the entry's id on the FAND File. Example: importlibrary Global as Global

External Library Import

External Library Imports are imports where the resource identifier is a quoted windows path. Relative paths are resolved relative to the FAND File used for project level compilation, or the current file for file level compilation. Example: importlibrary "./CombatOpening.nack" as opening

Action Import

Action Imports correspond to a monster's named list of actions. They correspond to the relevant monster named by the action import. Example: importactions Behemoth as behemoth, importactions Odogaron as clifford

It's possible to specify different action sets through compiler directives. This is the exhaustive list of legal action scopes for the default action set.

Anjanath Rathalos Aptonoth Jagras Zorah_Magdaros
Mosswine Gajau Great_Jagras KestodonM Rathian
Pink_Rathian Azure_Rathalos Diablos Black_Diablos Kirin
Behemoth Kushala_Daora Lunastra Teostra Lavasioth
Deviljho Barroth Uragaan Leshen PukeiPukei
Nergigante Xeno'jiiva KuluYaKu TzitziYaKu Jyuratodus
TobiKadachi Paolumu Legiana Great_Girros Odogaron
Radobaan Vaal_Hazak Dodogama Kulve_Taroth Bazelgeuse
Apceros Kelbi Kelbi2 Hornetaur Vespoid
Mernos KestodonF Raphinos Shamos Barnos
Girros Ancient_Leshen Gastodon Noios Magmacore
Magmacore2 Gajalaka Magmacore3 Tigrex Nargacuga
Barioth Savage_Deviljho Brachydios Glavenus Acidic_Glavenus
Fulgur_Anjanath Coral_PukeiPukei Ruiner_Nergigante Viper_TobiKadachi Nightshade_Paolumu
Shrieking_Legiana Ebony_Odogaron Blackveil_Vaal_Hazak Seething_Bazelgeuse Beotodus
Banbaro Velkhana Namielle Shara_Ishvalda Popo
Anteka Wulg Cortos Boaboa Alatreon
Gold_Rathian Silver_Rathalos Yian_Garuga Rajang Furious_Rajang
Brute_Tigrex Zinogre Stygian_Zinogre Raging_Brachydios Safi'jiiva
Scarred_Yian_Garuga Frostfang_Barioth Fatalis

Node

A node is a grouping of execution sections. It's equivalent to a subprocess in imperative languages. Nodes start with a Node Header, have a body consisting of Segments and end with the keyword endf. Nodes are executed sequentially, segment by segment. If execution reaches a node's end it does not return it halts.

Node Header

A node header consists of the keyword def followed by the Node Aliases. It can optionally be followed by an Explicit ID which is followed optionally by an Explicit Index

Node Aliases

Node aliases are a list of Unscoped Identifiers separated by &. They are used by Named Calls to jump execution to the node. During compilation node aliases are discarded and only used for resolving jumps.

Explicit ID

Explicit ID enable overriding the compiler's auto-generation of ids to explicitly assign an id to a node. An Explicit ID consists of : and a Numeric Literal. IDs are used by the underlying compiled THK for resolving external references.

Explicit Index

Explicit Indices enable specifying the node's index, overriding the file's positional ordering and the compiler's auto-generation of indices. An Explicit Index consists of @ and a Numeric Literal. Indices are relevant during compilation as it changes the ordering of nodes in the resulting file. Additionally compiled THK use indices for resolving internal references.

Node Format

The Node Format can roughly be read:

def alias0 & alias1 & ... & aliasn : ExplicitId @ ExplicitIndex
	Segment0
	...
	SegmentM
endf

An example of a node:

def distance_checks & node_004 : 240
	if self.distance_3d_to_target().leq(650) 
		self.enraged() >> node_014 => reset 
		>> node_013 => reset 
	elif self.distance_3d_to_target().leq(1600) 
		self.enraged() >> node_017 => reset 
		>> node_016 => reset 
	endif 
	return 
endf 

Segment

A segment is the smallest unit of reorderable code exection (a segment is however NOT atomic, parts of the segment are executed sequentially in a specific order).

A segment is either the finalizer, or a any combination of the following optional elements in the given order:

Formally speaking the sub-elements of a segment execute in the following order:

  • Function
  • Flow Control
  • Action
  • Call
  • Directive

A function without a flow control element in the segment represents an inline conditional and will skip the execution of the remainder of the segment.

Finalizer

The finalizer is a the keyword conclude that indicates a THK has finished executing and is used by interrupts to indicate they are handing control back to the main context or that the THK has no executbale code from the given entry points. It's most commonly seen in the Global THK's first node.

Segment Format

The Segment Format can roughly be read:

conclude

or

flowControl function -> action >> call => directive @ metadata

When directives are alone their symbol can be ellided

directive @ metadata

Example segments

if self.current_quest().is_rank(rank.AT) 

>> node_028 @ parameter2:1000

self.distance_3d_to_target().leq(700) >> node_022  => return 

Flow Control

Flow Control redirects the flow of execution locally by defining sub-execution scopes. There are two types of flow control operators, Conditionals and Randomization.

Conditionals

Conditionals alter the flow of execution based on the return of the Function within the same segment. Conditionals manipulate context and are not meaningful as single segments but as a contiguous grouping of segments within a node.

If

The keyword if opens a conditional scope which is only executed if the function on the same segment evaluates to true. It also starts the series of conditional scopes. A scope spans multiple segments until a scope close is found. It's a syntax error if there is no corresponding scope close before the node's end.

The following keywords are only valid if there's an already open conditional scope.

Else

The keyword else closes the previous conditional scope and opens a separate one. This scope is only executed if none of the previous scopes in the series of conditional scopes has evaluated true previously.

Else If

The keyword elif closes the previous conditional scope and opens a separate one. This scope is only executed if none of the previous scopes in the series of conditional scopes has evaluated true previously and the function within the same segment evaluated to true.

End If

The keyword endif closes the previous conditional scope and ends the series of conditional scopes. It cannot be followed by any other segment contents except metaparameters.

End With

The keyword endwith closes the previous conditional scope and ends the series of conditional scopes. It executes the remaining contents of the segment BEFORE closing the scope IF the previous scope was executed.

Conditional Formats

Conditional follows the following rough formats:

if function
	...
elif function
	...
else
	...
endif @ metaparameters

or

if function
	...
elif function
	...
else
	...
endwith function action directive @ metaparameters

Examples of conditional usage.

	if self.mounted() 
		-> behemoth.ride_rage_head() 
	else 
		-> behemoth.ride_rage_head_lv2() 
	endif 
	if function#10F() 
		>> node_077 
	elif self.current_quest().is_rank(rank.AT) 
		>> node_076 
	else 
		>> node_075 
	endwith >> node_084

Random

Randomization alter the flow of execution based on the result of a random value. Randomization Elements manipulate context and are not meaningful as single segments but as a contiguous grouping of segments within a node. Randomization Elements are accompanied by the percentage chance of its scope being executed. Only one scope is executed per series of randomization blocks.

The following two randomization elements must be followed by a parenthesis with an identifier that should resolve to an integer which is the chance of the subsequent scope being executed.

Random Start

The keyword random indicates the start of a random scope and a random sequence.

Random Else

The keyword elseRandom and elser close the previous randomization scope and opens a separate one.

End Random

The keywords endr and endrandom close the previous scope and terminates the seuqence of randomization blocks. It cannot be followed by any other segment contents except metaparameters.

End Random With

The keyword endwith and endrwith closes the previous conditional scope and ends the series of randomization blocks. It executes the remaining contents of the segment BEFORE closing the scope IF the previous scope was executed.

Random Formats

Random follows the following rough formats:

random(chance)
	...
elser(chance)
	...
endr @ metaparameters

or

random(chance)
	...
elser(chance)
	...
endrwith >> call => directive @ metaparameters

Examples of random usage.

random (80) >> node_070 
elser (20) 
endr 
random (LOW_CHANCE) 
			self.targetEnemy(21) @ parameter2:1000 
elser (LOW_CHANCE) 
			self.targetEnemy(22) @ parameter2:1000 
elser (MEDIUM_CHANCE) 
			self.targetEnemy(11) @ parameter2:1000 
elser (60) 
			self.targetEnemy(12) @ parameter2:1000 
endrwith >> 522

Function

Funcions correspond to ingame checks. Functions always have a boolean return, they might or might not have side-effects. Functions with side-effects as convention tend to have a fixed return of false. Using the ETL, it's possible to negate a function or a register comparison's return by prepending not.

Functions can take up to 2 meaningful parameters, but as part of their definition they can have multiple dummy parameters. Functions arguments additioanlly can belong to special scopes that are resolved separately from scopes.

Function signatures determine the syntax and positions of the arguments and parenthesis. This are specified by the FEXTY File used in the compilation process.

Example: function#52(), not function#32(), self.enrage(), not self.flying()

Function Literal

Function Literals allow specifying the function through their ID. They consist of the keyword function followed by the pound literal #, the function ID in hexadecimal and a matched pair of parenthesis. Optionally up to 2 arguments can be provided. Example: function#A2()

FEXTY Function

Non-Literal functions are always prefaced by the keyword self, however their syntax is entirely dependent on the FEXTY File used for compilation. The FEXTY File specification explains how to read FEXTY Files to produce the valid function expressions. Because this files run through a separate compilation process with significant freedoms, while the syntax is formalized it can be as expressive as the author of the file wishes. Leviathon provides a default fexty file and the exhaustive syntax of its functions is provided within the specification example.

An example from the default fexty file: self.targetEnemy(target_em.last_attacker)

Register

Register operations are considered functions in terms of their syntactical role, however they have unique syntax. Register operations are always enclodes in pairs of matching angled brackets [ ... ]. Register Operations can be classified in 2 ways. By the number of arguments they take: Unary and Binary Operations. By wether they are part of the default THK Register Operations or if they are part of the Extended THK Library. Extended operations are tagged as [ETL] and will simply not execute when the game interpreting the resulting THK is missing the ETL Plugin.

All register operations consist of minimally a register, which can be a Register Literal or a Register Identifier, and an operator represented as a valid symbol.

Register Literal

A register literal consists of the dollar sign $ followed by a valid register character (any capital letter from A to V). Example $S

Register Identifier

A register identifier is simply any valid Identifier. The identifier is treated separately from others and can only be coerced at project level within the FAND File. Multipler register usages throughout a project using the same identifier will be mapped to the same register on compilation. Example: RegVar1.

Unary Operation Register

Unary operations consist of a Register Identifier followed by either one of the two traditional unary operation literals or one of the three ETL unary operation literals.

This are:

  • |-: Reset the register to 0
  • ++: Increment the register by 1

The extended literals are:

  • --: Decrement the register by 1
  • ##: Write current system time to the register
  • #-: Write difference from stored time to current time to the register
  • ||: Writes the absolute value of the stored value to the register

Example: [$A ++], [RegVar |-]

Binary Operation Register

Binary operations consist of a Register Identifier followed by either one of the six traditional unary operation literals or one of the eight ETL binary operation literals, which is then followed by the operand. Without the ETL the operand must be an Identifier which correctly resolves to a numeric value. Within the ETL the operand can also be a non-conflicting Register Identifier. The non-conflicting condition means that the register identifier cannot be a valid numeric-resolving identifier within the current namespace otherwise it will simply resolve to a number first. This means the ETL allows operations between registers, which combined with the extended literals enables the full range of arithmetic operators and makes them function identical to regular assembly registers.

The binary comparison literals are:

  • ==: Equal
  • <=: Lesser Equal
  • <: Less Than
  • >=: Greater Equal
  • >: Greater Than
  • !=: Not Equal

The extended literals:

  • := Assigns directly to the register
  • += Adds and stores the result on the register
  • -= Substracts from and stores the result on the register
  • *= Multiplies and stores the result on the register
  • /= Divides by the operand and stores the result on the register
  • #> Checks if time elapsed relative to the value stored on the register is greater than the operand
  • #< Checks if time elapsed relative to the value stored on the register is less than the operand
  • %= Modulos by the operand and stores the result on the register

All of the ETL binary operators except the Time Elapsed checks return false.

Examples: [RegisterVar1 == 1], [FlightTimer #< 6000]

Function Arguments

Call

A Call modifies the segment pointer to the start of the target node. At high level the target node might be on a separate file. Whenever a call is executed the current segment position is placed on a stack. The return directive pops this stack and continues execcution on the line AFTER the call.

All calls must be prepended by the call sign >>.

Call Literal

A call literal consists of the keyword call followed by the pound literal # and finally the node index in decimal. Example: >> call#122.

Local Call

A local call consists of an identifier. The identiier must resolve to an existing node alias in the same NACK file. Example: >> node_000

Scoped Call

A scoped call consists of a scoped identifier. The identifier must resolve to an existing node alias in the import corresponding to the scope. Example: >> Global.node_032

Scoped Call Literal

A scoped call consists of an identifier, the dot literal ., the pound literal # and finally the node id in decimal. The identifier must resolve to an existing scope. Example: >> Global.#152

Directive

Directives modify the segment pointer, which determines the next segment that will be executed.

Return

The keyword return sets the segment pointer to the segment which follows the call to the current node.

Repeat

The keyword repeat sets the segment pointer to the start of the current node.

Reset

The keyword reset resets the segment pointer to the first node of the THK that corresponds to the active context (a reset inside the Global THK will reset to the first node of the THK which called it).

Action

Actions map to monster actions. Monster actions correspond to a set of animations, shell and collision calls, and executable code mapped to the respective action class.

All actions must be prefaced with the action sign -> and be followed by matched parenthesis pair containingthe action arguments (empty parenthesis will pass the default arguments).

Action Literal

An action literal consists of the keyword action followed by the pound literal # and finally the action id in hexadecimal. Example: -> action#2B()

Scoped Action

A scoped action consists of a scoped identifier. The scope must correspond to an action import and the target must correespond to a valid action with the action import. Example: -> behemoth.normal_turn()

Action Argumets

An action can have up to 5 arguments. Each argument must be an identifier and resolve to a number. Example: -> behemoth.dying_walk(2,Global.PARAMETER_VAL,0,PARAMETER_CONSTANT)

Numeric Literal

A numeric literal ccan be either a decimal literal or a hexadecimal literal. A decimal literal consists of a string of numeric characters that start with a non-zero character. A hexadecimal literal consists of numeric characters or the letters A to F, it must be prefaced with the hex identifier 0x. This preface is omitted in function literals which are hexadecimal in all instances. Example: 0xDEADBEEF, 314159, 0x1E62A32.

Identifier

Local Identifier

Local or Unscoped Identifiers consist of any alphabetic character followed any number of alphanumeric characters or the underscore or the apostrophe character ([A-Za-z][a-zA-Z_'0-9]*). Local Identifiers resolve only on the current namespace. Example: Safi'Jiiva, node_000, integral_area,varNumber5.

Scoped Identifier

A scoped identifier is a pair of Local Identifiers separated by a dot literal .. The first identifier must correspond to an imported scope name, either a Library Import or an Action Import or one of the reserved scopes. The seccond identifier must be a valid identifier within the scope define by the first identifier. Example: Global.node_005, Global.DISTANCE_MID, behemoth.walk, ImportedLib.CHANCE_LOW.

Metadata

A metadata declaration is preceded by the meta literal @. It consists of pairs of THK Field and numeric literal, separated by a colon :. The valid THK Field names are:

endRandom flowControl branchingControl unkn1 unkn2 functionType parameter1 unkn3
unkn4 comboSetting unkn6 parameter2 nodeEndingData extRefThkID extRefNodeID localRefNodeID
unkn7 unkn8 unkn9 unkn10 unkn11 actionID actionUnkn0 actionUnkn1
actionUnkn2 unkn12 actionUnkn3 actionUnkn4 unknExtra0 unknExtra1 unknExtra2

It's NOT RECOMMENDED to use metadata identifiers directly for fields that have non-metadata syntactical constructs. The compiler does not use metadata fields until the last phases of compilation, as a result there's a significant absencce of error checking on the data placed on this section.

Compilation Mechanisms

Decompiler Options

positional arguments:
  input                 File to decompile (.thk or thklst)

optional arguments:
  -h, --help            show this help message and exit
  -nullShow             Keep empty nodes on decompiled output
  -fixShow              Keep placeholder empty instruction for decompiler corrections
  -lastShow             Keep the last node even if empty
  -indexShow            Preserve each node's index in the file
  -idShow               Preserve node Id in the file
  -noActions            Do not resolve actions to names
  -raiseInvalidReferences
                        Stop decompilation if an illegal call is found
  -warningsHide         Hide decompilation warnings as comments on offending segments
  -keepRegisters        Keep registers as fixed identifiers during decompilation
  -renameNackFiles      Rename .nack files to their function
  -analyze              Analyse the code flow of the project and report on action, register and call usage
  -outputPath OUTPUTPATH
                        Root folder where the decompiled files will be outputted
  -analysisOutputPath ANALYSISOUTPUTPATH
                        Folder to output the results of the code analysis
  -fexty FEXTY          Forked Functional Extension input file

Input

Either the THKLST file for project level decompilation or the THK file for file level decompilation. If you intend to edit more than one file, or the global THK DO NOT USE FILE LEVEL DECOMPILATION.

Keep Empty Nodes

-nullShow

Preserves empty nodes instead of elliding them in decompilation results. Relevant if something is calling empty nodes (none of the game''s THK do this thankfully). Generally should not be used.

Generate Placeholder Empty Instructions

-fixShow

Generates empty instructions when one is commented out due to illegal context. This is useful if for some reason one wishes to preserve segment count despite illegal instructions inflating said count. Generally should not be used.

Keep Last Node

-lastShow

Preserves the last node and adds an explicit index. This ensures the node count on compilation is preserved. Generally should not be used.

Write Indices

-indexShow

Writes the index of each node to its header. This ensures nodes are on the same index on compilation. Generally should not be used.

Write IDs

-idShow

Writes the index of each node to its header. This ensures nodes use the same index on compilation. Generally should not be used. It has a use ccase when one (against previous advice) is editing a Global THK at file level. Excessive care should be taken to ensure that references from other THKs are not altered in such a way that it breaks the expectations of the callers. Additionally it might prove usefull when updating non-Leviathon edited THKs as it ensures node ids are kept consistent.

Preserve Actions as Literals

-noActions

Does not resolve actions to the monster scope declared on the file but keeps them as Action Literals. Not recommended for decompiling in-game THKs but proves useful when dealing with non-standard edits made without any preservation of the format specification.

Stop at Invalid References

-raiseInvalidReferences

Stops decompilation if an invalid call dereference is found. Useful to determine if the file one is decompiling will function or re-compile properly.

Hide Warning Comments

-warningsHide

Hide warning comments generated by the Decompiler when fixing issues with the original file during the decompilation process.

Hardcode Register Names

-keepRegisters

Writes a hardcoded assignment of each register variable into the FAND file. This ensures that registers will compile to the same letter when being written back. Generally should not be used.

Rename Nack Files to Function

-renameNackFiles

Names the resulting NACK files to the function they have on the THKLST, for example instead of em000_55.nack it would produce Global.nack.

Analyze

-analyze -analysisOutputPath

Produces a code analysis report at the specified folder path with the filename of analysis.txt.

Output Path

-outputPath

Specificifies the folder where the decompilation process will output files.

Input Custom FEXTY File

-fexty

Allows inputing a custom .fexty file for resolving function ids into function names and argument structure.

Compiler Options

positional arguments:
  input                 Project file to compile (.fand)

optional arguments:
  -h, --help            show this help message and exit
  -verbose              Print intermediate compilation process information
  -display DISPLAY      Output compilation reports to the given file
  -monLib MONLIB        Override Default Monster Library
  -fexty FEXTY          Override Default Function Resolver (.fexty)
  -thkNames THKNAMES    Override Default THK Names
  -directForeign        Use Direct Reference to Foreign Imports instead of inlining
  -inlineGlobal         Inline Global Functions
  -projectNames {function,nackFile,index}
                        Function: Outputs the thks names as their scope name. 
			Nackfile: Outputs the thks names as the name of the nackfile. 
			Index: Outputs the thks names as their index in the thklst
  -preprocessor PREPROCESSOR
                        [Non-Standard] Run the macro prepropcessor
  -symbols              Export Debugging Symbols
  -skipVerify           Skip internal compiler state verification during compilation
  -forceCritical        Convert all errors into critical errors that automatically stop compilation
  -forceError           Convert all warnings into errors
  -repeatedProperty REPEATEDPROPERTY
                        Error level for repeated properties [Warning,Error,CriticalError]
  -outputName OUTPUTNAME
                        Output THKList Name
  -outputRoot OUTPUTROOT

Input

Verbose

-verbose

Outputs all compilation intermediate phases.

Display

-display

Set a file as output for ccompiler messages.

Monster Library

-monLib

Set a folder with custom monster action listings to be used as the monster library during compilation.

Fexty File

-fexty

Set a custom FEXTY File to resolve function names and argument positions.

THK Mapping:

-thkNames

Overrides the default THKLST index to function mapping with a custom one.

Foreign Imports as External Calls

-directForeign

Treat internal imports from non-Global nodes as external calls, reeplacing the inlining operation to substituting it for an external call with the THK Index as its External THK ID and the node ID as External Node ID.

Inline Global Nodes

-inlineGlobal

Treat calls to the Global THK as inline functions. Performing the inlining operation instead of resolving to a single segment call.

Project Naming Scheme

-projectNames

Options:

Function

Names the output THK to the THKLST context connected to the THK. Example: Global.thk

Nackfile

Names the output THK after the NACK file used to compile it.

Index

Names the output THK after the output THKLST and the THKLST index of the node. Example: em001_02.thk

Preprocessor

-preprocessor

Executes the Macro preprocessor on all of the NACK files before performing the compilation. Details on the Preprocessor

Symbols

-symbols

Exports debugging symbols for use with the THK Debugger. This enables line number on nodes, and readable segment names.

Skip Verify

-skipVerify

Skips the compiler internal state verification which ensures there's no broken inheritance produced through erroneous in-lining or invalid internal states that result in suppressing errors. Skipping verification improves performance but might hide compilation errors.

Force Critical

-forceCritical

Treats all errors as critical errors and immediately stops compilation on them.

Force Errors

-forceError

Treats all warnings as errors.

Repeated Property

-repeatedProperty

Set the error level for property repetition (due to metadata entries adding fields that already exist on the segment.

THKLST Name

-outputName

Set the output thklst name. Defaults to em000.thklst.

Output Folder

-outputRoot

Set the folder where the compiled files will be output. Defaults to the folder with the FAND File.

Macros

Macros are an optional aspect of the language that are not part of the langage specification and are providedd as a convenience. Macro preprocessing must be specified as a compile option.

Macros consists of macro declarations and replacement points. Declarations are structured as: ~~macroName(argumentName0,...,argumentNameN) ! replacementFormat The corresponding invocation is structured as: contextLeft ~macroName(argumentName0,...,argumentNameN) contextRight Where macroName is the name for the macro which will be used to invoke them in further lines. Argument Names are used as replacement mappings in the replacementFormatString. The Left and Right Context are kept intact.

Example:

~~wait(WAITTIME) ! def waitNode_WAITTIME \[WaitVar ##]\>>waitLoop_WAITTIME\return\endf\def waitLoop_WAITTIME \[WaitVar #-] return\repeat\endf

~wait(50)
~wait(100)
~wait(150)

def startNode
	-> monster.take_off >> waitNode_50
	-> monster.land >> wait_Node_150
endf

Resolves to

~~wait(WAITTIME) ! def waitNode_WAITTIME \[WaitVar ##]\>>waitLoop_WAITTIME\return\endf\def waitLoop_WAITTIME \[WaitVar #-] return\repeat\endf

def waitNode_50 
	[WaitVar ##]
	>>waitLoop_50
	return
endf

def waitLoop_50
	[WaitVar #-] return
	repeat
endf

def waitNode_100 
	[WaitVar ##]
	>>waitLoop_100
	return
endf

def waitLoop_100 
	[WaitVar #-] return
	repeat
endf

def waitNode_150 
	[WaitVar ##]
	>>waitLoop_150
	return
endf

def waitLoop_150 
	[WaitVar #-] return
	repeat
endf


def startNode
	-> monster.take_off >> waitNode_50
	-> monster.land >> wait_Node_150
endf

The macro system is extremely rudimentary. It performs a direct replcement of all ocurrences of argument names in the replacement format. Because the argumetn names are used as a string replacement, it's recommended to make them sufficiently unique to avoid spuriours replacements. Macro evaluation occurs simultaneously on all arguments, as a result, the result of an earlier argument will not be a match for succesive arguments.

Inlining

The THK Low-Level specification does not have a notion of imports. It's only external node option is when calling nodes from the Global THK, as a result, to be able to implement imports it's necessary to either extend the game or perform an inlining procedure. Inlining consists of making a copy of the node in the file performing the import. However this also requires importing ALL of the nodes on which this node depends.

The Inlining pass is a recursive depth first procedure where inlines are resolved bottom up. On each step it resolves Caller scope identifiers and calls. If multiple files import a single file, the inlining procedure will treat each as a separate file, as a result multiple files with a single dependency do not crosscontaminate on caller scope resolution.

Explicit ID and Index Overrides

Its possible to explicitly set node id and indices. Explicit Indices are used to pre-fill the node list on a THK and the remaining nodes fill around them. Similarly explicit IDs are used to seed the automatic id generator. In the case of ID and index collisions an exception will be raised.

Register Resolution

Registers can be harcoded to specific letters on the FAND File. During compilation the FAND File is parsed first for hardcoded register mappings and register names. Then all of the FAND File NACK files are parsed to extract register identifiers. From the complete list each register is assigned an unused letter. In case there are more registers than there are possible letters the compiler outputs an error.

FAND Files

FAND Files are project orchestrators which specify the mapping of THK to concrete game situations. The function on the ith slot is consistently used for the same purpose in most cases. This is why Global THK are often referred as THK 55, as this is the index in the THKLST which determines what file is used for the Global Table.

FAND Files consist of an optional Chunk Path, followed by a mandatory Relative Path, followed by an optional Monster Specifier, followed by an optional list of Register Declarations, followed by a list of Scope Resolution Entries and finally an optional Entry Count.

Example:

at D:\Games SSD\MHW\chunk
through em\em121\00\data
is Behemoth

Register AreaChangeCommunication
Register GlobalVar0 as $B

Combat_Main = tu0Event/em121_00.nack @ 1131A51E
THK_06 = em121_03.nack @ 0A3FC6CC
Rage_Enter = em121_07.nack @ 0A3FC6CC
Global = em121_55.nack @ 54F7F8EC

has 72 entries

Chunk Path

Optionally specifies the chunk location. Discarded by the compiler, mostly for documentation purposes. Prepended by the keyword at

Relative Path

Specifies the path to prepend to all of the NACK File paths during compilation. Corresponds to the relative path to the highest common folder between the THKLST and all of its summoned THKs. Prepended by the keyword through.

Monster Specifier

Optionally specifies to which monster action resolution scope all of the generic monster scope within the NACK files will resolve. Prepended by is

Register Declaration

Optionally reserves identifiers as registers, optionally can hardcode the target register letter trough the keyword as and prepending the dollar sign $ to the register letter. Prepended by the keyword Register. Example: Register RegisterVar4 as $V

Scope Resolution Entry

A scope resolution entry maps a NACK File to a THKLST entry. Consists of either a known context or an indexed THK identifier of the form THK_%02d, followed by an equality sign =, followed by the implementation path, and optionally a metadata specifier prefaced by the at sign @. At the time it is not known what this metadata specifier does. The metadata specifier is in hexadecimal by default. Example: THK_29 = em121_29.nack @ 8A8254DB, Global= em092/05/data/em092_55.nack @ 8A8254DB The list of known contexts:

THK_00 = Combat_Main
THK_01 = Combat_Enter
THK_02 = Search
THK_03 = Discover
THK_07 = Rage_Enter
THK_09 = Exhaust_Enter
THK_10 = Exhaust_End
THK_11 = Mount
THK_15 = Non_Combat_Main
THK_17 = Combat_Area_Change
THK_18 = Non_Combat_Hit
THK_20 = Combat_Critical_HP
THK_23 = Combat_Turf_War_A
THK_36 = Combat_Blinded
THK_37 = Combat_Blinded_Recovery
THK_50 = Combat_Turf_War_B
THK_55 = Global

Entry Count

Optionally specifies the size of the THKLST list (filling empty spaces with empty paths). Prepended by the keyword has, optionally can have the keyword entries after but the compiler discards it regardless.

FEXTY Files

FEXTY Files provide function resolution mechanisms through a simple syntax. The language is kept relatively simple but highly extensible. Permitting strong expressive constructions for use with Leviathon.

FEXTY Files consist of an optional numerical type specification (Use hex will cause all values in the file to be read as hexadecimal without a starting prefix), followed by a series of function id resolution rules.

Using a substitute FEXTY file will completely replace the default (not extend). So if you want to add functionality you will have to start from the default file (found here) and add your extensions from there. You might also modify existing entries if you want to re-define elements of the function call syntax or targets.

Function String

A function string consists of identifiers optionally followed by a matched parenthesis pair with either comma separated replaceable arguments or a dummy identifier inside, which is in turn followed by a dot ..

Example: distance_3d_to_target().leq(parameter1), target_em.random_player_or_cat

Replaceable Argument

A replaceable argument is any THK Field Name. During ccompilation this are treated as formal arguments and will accept numeric values and evaluate the relevant field to the value passed as argument on the given position.

Its possible to specify special enum elements. This translate a parameter to a scoped identifier during decompilation and a scoped identifier to an integer during compilation. This are expresed through a matching brackets expression with the scope separated from the THK Field by a colon :. The 3 special translation elements are: em_enum and st_enum. Example: in_map({st_enum:parameter1}).in_area(parameter2)

Dummy Identifier

Dummy identifier are strings not meant to resolve to anything and simply acting as syntactic sugar for function decompilation and compilation. Example: current_quest().is_rank(rank.HR)

Inline Rule

An inline rule consists of the function id in the specified numerical format continued by a colon : and followed by a single function string.

Example: 1A: angular_1A(parameter1,parameter2)

FEXTY Rule

A rule consists of the function id in the specified numerical format, continued by a bracket which starts a scope which must be evnetually closed. The scope consists of an optional prefix which will be included in all of the successive function strings, followed by a series of resolution rules. Example:

3{
  >targetEnemy
  parameter1 == 1 : (target_em.random_player_or_cat)
  parameter1 == 2 : (target_em.closest_entity)
  parameter1 == 13 : (target_em.last_attacker)
  parameter1 == 29 : (target_em.any_monster)
  parameter1 == 41 : (target_em.nearest_monster)
  parameter1 == 66 : (target_em.last_target)
  otherwise : (parameter1)
}

Which produces the following rules:

self.targetEnemy(target_em.random_player_or_cat)
self.targetEnemy(target_em.closest_entity)
self.targetEnemy(target_em.last_attacker)
self.targetEnemy(target_em.any_monster)
self.targetEnemy(target_em.nearest_monster)
self.targetEnemy(target_em.last_target)
self.targetEnemy(parameter1)

Where all of the functions except the last are basically literals, and the last takes a single argument which is then assigned to the parameter1 field on compilation.

Resolution Rule

A resolution rule consists of comma separated list of pairs of THK Field Name and a numerical value, separated by the equality comparison == or the fallthough rule otherwise which catches any set of values not specified by previous rules. Reoslution rules are used during decompilation to map function id and segment parameters into a function string, and during compilation to map functions into their low level binary representation. Example parameter1 == 1 , parameter2 == 0, otherwise

Function Enumeration

The following is the list of all function rules provided by the default fexty file:

self.targetEnemy(target_em.random_player_or_cat)
self.targetEnemy(target_em.closest_entity)
self.targetEnemy(target_em.last_attacker)
self.targetEnemy(target_em.any_monster)
self.targetEnemy(target_em.nearest_monster)
self.targetEnemy(target_em.random_player)
self.targetEnemy(target_em.last_target)
self.targetEnemy(arg:parameter1)
self.targetUnknown(arg:parameter1,arg:parameter2)
self.targetArea(target_area.nearest_entrance)
self.targetArea(target_area.next_exit)
self.targetArea(target_area.global_center)
self.targetArea(target_area.area_center)
self.targetArea(target_area.area_aerial_center)
self.targetArea(target_area.nearest_monster_area)
self.targetArea(target_area.unknown_aerial)
self.targetArea(target_area.unknown_ground)
self.targetArea(arg:parameter1)
self.distance_3d_to_target().leq(arg:parameter1)
self.distance_3d_to_target().gt(arg:parameter1)
self.distance_2d_to_target().leq(arg:parameter1)
self.distance_2d_to_target().gt(arg:parameter1)
self.distance_3d_recalc_to_target().leq(arg:parameter1)
self.distance_3d_recalc_to_target().gt(arg:parameter1)
self.distance_2d_recalc_to_target().leq(arg:parameter1)
self.distance_2d_recalc_to_target().gt(arg:parameter1)
self.vertical_distance_to_target().leq(arg:parameter1)
self.vertical_distance_to_target().gt(arg:parameter1)
self.above_target()
self.below_target()
self.above_area()
self.below_area()
self.angle_2d_ccw_between(arg:parameter1,arg:parameter2)
self.angle_2d_cw_between(arg:parameter1,arg:parameter2)
self.angular_18(arg:parameter1,arg:parameter2)
self.angular_1A(arg:parameter1,arg:parameter2)
self.angular_15(arg:parameter1,arg:parameter2)
self.angular_17(arg:parameter1,arg:parameter2)
self.angular_19(arg:parameter1,arg:parameter2)
self.angular_1B(arg:parameter1,arg:parameter2)
self.in_combat()
self.alert_out_of_combat()
self.enraged()
self.fatigued()
self.poisoned()
self.defense_downed()
self.miasmaed()
self.hookable()
self.target_on_part(arg:parameter1)
self.mounted()
self.mount_finisher_ready()
self.mount_stabbed()
self.mount_staggered_twice()
self.target.pinned()
self.enrage_time_left().leq(arg:parameter2)
self.fatigue_time_left().leq(arg:parameter2)
self.hp_percent().leq(arg:parameter2)
self.target.helpless_0()
self.target.helpless_1()
self.target.in_combat()
self.target.poisoned()
self.target.deadly_poisoned()
self.target.bleed()
self.target.blast_blighted()
self.target.paralyzed()
self.target.stunned_0()
self.target.sleeping()
self.target.fire_blighted()
self.target.thunder_blighted()
self.target.water_blighted()
self.target.ice_blighted()
self.target.dragon_blighted()
self.target.defense_downed()
self.target.stunned_1()
self.target.miasmaed()
self.target.mudded()
self.target(arg:parameter1)
self.target_is(em_enum:parameter1)
self.quest_id(arg:parameter2)
self.flying()
self.part(arg:parameter1).is_broken(arg:parameter2)
self.force_area_change()
self.force_area_change2()
self.current_quest().is_rank(rank.LR)
self.current_quest().is_rank(rank.HR)
self.current_quest().is_rank(rank.MR)
self.current_quest().is_rank(rank.AT)
self.in_map(st_enum:parameter1).in_area(arg:parameter2)

The following functions correspond to the ETL:

self.heal(arg:parameter2)
self.heal_perdecmil(arg:parameter2)
self.damage(arg:parameter2)
self.damage_perdecmil(arg:parameter2)
self.hp.return_rax()
self.max_hp.return_rax()
self.stamina.increase(arg:parameter2)
self.stamina.increase_perdecmil(arg:parameter2)
self.stamina.return_rax()
self.enrage()
self.refresh_enrage()
self.corpseDuration(arg:parameter2)
self.corpseDuration.return_rax()

General Tutorials

General Tutorials

Animation Tutorials

Animation Tutorials

Audio Tutorials:

Audio Tutorials

IDs:

File & In Game IDs

Model Tutorials:

Model Tutorials

Effects Tutorials:

EFX Tutorials

FSM Tutorials

FSM Editing

MRL3 Tutorials:

MRL3 Tutorials

NPC Editing:

NPC Editing

Map Editing:

Map Editing

Plugins and Memory Editing:

Plugins and Memory Editing

Quest Editing:

Quest Editing

Monster AI Editing:

Monster AI Editing

Texture Tutorials:

General Texture Tutorials
Specific Texture Tutorials

TIML Editing

TIML Editing

Asterisk's Plugin Notes:

Asterisk's Plugin Notes

Miscellaneous Tutorials:

Miscellaneous Tutorials

Outdated Tutorials:

Outdated Tutorials
Clone this wiki locally