diff --git a/docs/zengin/anims/.pages b/docs/zengin/anims/.pages
index 84b9c152a5..f9fc0a6d68 100644
--- a/docs/zengin/anims/.pages
+++ b/docs/zengin/anims/.pages
@@ -1,3 +1,5 @@
nav:
+ - naming.md
- ...
+ - events.md
- tutorials
\ No newline at end of file
diff --git a/docs/zengin/anims/events.md b/docs/zengin/anims/events.md
index ed2f2a43b1..9164c407c8 100644
--- a/docs/zengin/anims/events.md
+++ b/docs/zengin/anims/events.md
@@ -61,10 +61,9 @@ General Syntax:
| [eventSFX](#eventsfx) | create sound effect |
| [eventSFXGRND](#eventsfxgrnd) | create sound effect on the ground |
| [eventTag](#eventtag) | generic event, does action specified in parameters |
-| Defined in engine but never used ? | |
| [eventPFXGRND](#eventpfxgrnd) | create particle effect on the ground |
| [eventSetMesh](#eventsetmesh) | ? |
-| [modelTag](#modeltag) | same as eventTag, but applies to morphmesh? |
+| [modelTag](#modeltag) | same as eventTag, but applies to all amiations in aniEnum |
### eventCamTremor
@@ -125,7 +124,7 @@ Syntax:
Both `INTENSITY` and `HOLD_TIME` can be specified in the MMS script. All gothic morph meshes specify those values in .MMS, therefore behavior when both specified in eventMMStartAni and .MMS file is unknown/untested
-## eventPfx
+### eventPfx
Start particle effect at the specified bone.
@@ -155,7 +154,7 @@ Syntax:
`ATTACH` is used to create demons burning hand during the attack, while without this keyword dust particles are made to stay at the position where NPC landed after falling.
-## eventPFXStop
+### eventPFXStop
Stops particle effect previously started by [eventPfx](#eventpfx)
@@ -177,7 +176,7 @@ Syntax:
`PFX_HANDLE` - an integer value. *Handle* of the particle effect, that should be destroyed. Particle effect must be spawned using the same handle by [eventPfx](#eventpfx) first
-## eventSwapMesh
+### eventSwapMesh
Move mesh from source `NODE` to target node. Item should be present in the node already. Only mesh of the Items is moved, engine internally still keeps a reference to items in the original slot? Never used in game?
@@ -201,7 +200,7 @@ Syntax:
!!! Note
In some rare occasions duplicates item
-## eventSfx
+### eventSfx
Play sound effect. It can be either `SFX` instance from scripts, or `.WAV` file.
@@ -230,7 +229,7 @@ Syntax:
A lot of original game animations contain `EMTPY_SLOT` instead of `EMPTY_SLOT` which was probably unintended. Gothic therefore acts as no keyword was provided, which causes a lot of sound interruptions. Therefore be mindful of spelling when copying original MDS scripts
-## eventSfxGrnd
+### eventSfxGrnd
the same as [eventSfx](#eventsfx) with only one difference, the sound effect name is appended with the current material name.
@@ -263,7 +262,7 @@ Depending on the material of the texture, the character is standing on, the game
NPC running on grass texture, with material set to EARTH in world editor, will play sound `Run_Earth` by using `*eventSFXGrnd (12 "Run")` in run animation. `_Earth` suffix is determined and added by the engine.
-## eventTag
+### eventTag
This is a generic type of event that does different actions based on the first parameter after the frame parameter. It was probably later in development to extend MDS functionality without the need to expand parser itself.
All parameters except `FRAME` are passed inside quotes Further parameters are specific for every `EVENT_TAG_TYPE`.
@@ -848,27 +847,25 @@ ani ("s_1hAttack" 1 "s_1hAttack" 0.0 0.1 M. "Hum_1hAttackComboT3_M05.asc
+### eventPfxGrnd
-
-## eventPfxGrnd
-
-Not used anywhere in the original game. Could possibly spawn particle effect like [eventPfx](#eventpfx) but with an added suffix similar to how [eventSfxGrnd](#eventsfxgrnd) works. Needs to be investigated.
+Not used anywhere in the original game. Probably meant to spawn particle effect like [eventPfx](#eventpfx) but with an added suffix similar to how [eventSfxGrnd](#eventsfxgrnd) works. In practice, it does nothing.
Syntax:
```dae
*eventPFXGRND (FRAME)
```
-## eventSetMesh
+### eventSetMesh
Unknown
Syntax:
```dae
-*eventSETMESH (FRAME "NODE_NAME")
+*eventSETMESH (FRAME "ASC_NAME")
```
-## modelTag
+### modelTag
Should work similarly to [eventTag](#eventtag), but can be defined inside aniEnum block and applies to all animations of the Model.
diff --git a/docs/zengin/anims/index.md b/docs/zengin/anims/index.md
index dcfeebb98f..21e7f943da 100644
--- a/docs/zengin/anims/index.md
+++ b/docs/zengin/anims/index.md
@@ -1,50 +1,93 @@
# Animation
+Animations in ZenGin consist of animation files and animation scripts. Working with them requires deep understanding of the architecture and concepts. This section provides information necessary to effectively work with animations within the engine.
+## Types
+There are two main types of animations - `skeletal` and `morphmesh`. Skeletal animations are used for animating characters and objects with a skeleton, while morph mesh animations are used for animating facial expressions and other models that require vertex-based deformations.
-# Animations in ZenGin
-Animations are (apart from maybe advanced programming work using Ikarus or Union) one of the most advanced modding techniques, since you not only must understand the way they work, but also know how to write the animation script and understand the whole scheme selection system, naming convention and of course know how to animate (that is my biggest problem :D). To get a new animation into ZenGin (the Gothic engine) is not difficult per se, I would describe it as tedious.
+## Formats
-Luckily, there are tools to help us to achieve our goal - get a new animation to be used by the engine, and in effect, to be used and seen in the game.
+### Raw
-To describe the whole process, I constructed this small tutorial, to help other people to get animations working and to spare them many hours of searching the excellent forum posts, that describe parts of the process.
-__
+**Animation files**
-Excluding advanced programming work with Ikarus or Union, animations are arguably the most advanced modding discipline of ZenGin engine. Its difficulty stems for the fact that you not only have to understand the general concept, but also learn how to write the animation scripts and understand the whole scheme selection system, including naming conventions and, most important for last - actually know how to animate. Adding new animations into ZenGin is more tedious than actually difficult.
+ZenGin uses the `.ASC` file format for raw animation files. Following things could be saved as the `.ASC` file:
-There are tool to help with this endeavor - to get a new animation implemented in the engine, and seeing its effects in game. Following tutorial has been constructed to help others to get their animations working without having to scour old forum posts for hours.
+- Animation model (skeleton + skin or morph mesh)
+- Actual animation data (keyframes)
+- Animation targets (like doors, beds etc.)
-## Prerequisites - Tools & Materials
-1. Gothic Mod Development Kit (MDK)
- - Gothic 1 MDK - [link](https://github.com/PhoenixTales/gothic-devkit)
- - Gothic 2 MDK - [link](https://www.worldofgothic.de/dl/download_94.htm)
-2. [Blender](https://www.blender.org/)
-3. [Kerrax's Import Export plugin](https://gitlab.com/Patrix9999/krximpexp) - follow the installation instructions to install the plugin, make sure to set up the texture paths too
-4. Tool for decompiling animations [GothicSourcer](https://worldofplayers.ru/threads/41942/), or use [phoenix](https://github.com/lmichaelis/phoenix) or write your own using [ZenLib](https://github.com/Try/ZenLib)
+The `.ASC` files can be imported into blender using the [KrxImpExp](#krximpexp) add-on.
+**Script files**
-## The workflow
-This is the basic step-by-step workflow on how to get the animation into the game.
+Every animated model in ZenGin has its own animation script file with the `.MDS` extension for skeleton based models and `.MMS` for morph mesh based models. These files are used to define animation data. You can find more about these files in the [MDS](mds.md) and [MMS](mms.md) sections.
-1. Load the actor (character or object) into your 3D software
-2. Create your animation
-3. Export the animation as an `.asc` file
-4. Write the MDS file
-5. Run the game to compile your animations
-6. Test your animations in-game using a Daedalus script or a console command
+### Compiled
+Raw formats are great for editing, but can be a bit heavy for the engine use (ASCII based formats are slower to parse and work with), because of that ZenGin compiles these animations into various internal formats.
-Sounds simple enough, except there is a lot missing. Even though the steps start with loading the actor into blender, understanding the system of animations to get high quality assets into your mod is more important.
+| Format | Extension | Content |
+|--------|-----------|-------------|
+| Model Animation | `.MAN` | Single animation data |
+| Model Hierarchy | `.MDH` | Skeletal structure and slots for a model |
+| Model Mesh | `.MDM` | Softskin mesh and proto mesh structure for a model |
+| Model | `.MDL` | `.MDH` + `.MDM` combined |
+| Model Script | `.MSB` | Compiled version of the `.MDS` file |
+| Morph Mesh | `.MMB` | Morph mesh with its mesh and animation data |
-## Animation "types"
-There are two main types of animations - `skeletal` and `morphmesh` animations. Character body animations are skeletal, and we animate the skeleton and the entire model (skin) moves around it. Morph mesh animation is, on the other hand, used for facial animations such as eating, blinking or talking and for animated meshes like wave water ferns or fish in Khorinis' harbor.
-This guide focuses on skeletal animations. There are few different ones, all of which will have their own demonstration in the future. Categories are:
-1. Standalone animation - waving, bowing, eating
-2. MOBSI animations - bed, alchemy table, anvil
-3. Item animations - sweeping the floor with a broomstick, using the horn, playing the lute
-4. Mandatory animations - running, walking, sneaking
-5. Combined/interpolated animations - picking stuff up, aiming with a bow/crossbow
+## Engine compilation
+By default, animations are compiled by the engine when the model appears in the game or a game is started with `-zconvertall` parameter.
-All of these animations are defined in an MDS file which will be talked about in the next sections.
+!!! Danger
+ If you want to recompile animations, make sure that the `.MSB` or `.MMS` files are deleted, including the ones in the VDF volumes, otherwise the engine will not recompile them.
+
+### Graphical overview
+
+A star `*` means that multiple files could be used/generated.
+
+
+
+=== "Skeletal Animation"
+ ``` mermaid
+ flowchart TD
+ A[Model .ASC] --> D(Compilation)
+ B[Animation .ASC *] --> D
+ C[Model Script .MDS] --> D
+ D --> E[Compiled Model Script .MSB]
+ D --> F[Model Animation .MAN *]
+ D --> G[Model Hierarchy .MDH]
+ D --> H[Model Mesh .MDM]
+ ```
+
+
+=== "Morph Mesh Animation"
+ ``` mermaid
+ flowchart TD
+ A[Model .ASC] --> D(Compilation)
+ B[Animation .ASC *] --> D
+ C[Morph Mesh Script .MMS] --> D
+ D --> E[Morph Mesh .MMB]
+ ```
+
+
+
+
+## Tools
+
+### KrxImpExp
+Blender add-on that implements support for Gothic 3D formats.
+
+[:fontawesome-brands-gitlab: GitLab](https://gitlab.com/Patrix9999/krximpexp)
+
+### Gothic Sourcer
+Tool for decompiling animation files.
+
+[:octicons-arrow-right-24: Read more](../tools/gothic_sourcer.md)
+
+### ZenKit
+C++ Library for loading and saving various ZenGin files (including some animation formats).
+
+[:octicons-arrow-right-24: Read More](../tools/libraries/zenkit.md)
diff --git a/docs/zengin/anims/mds.md b/docs/zengin/anims/mds.md
index 228f4bff12..9ead2c783c 100644
--- a/docs/zengin/anims/mds.md
+++ b/docs/zengin/anims/mds.md
@@ -2,66 +2,78 @@
title: MDS ModelScript
---
-# MDS - model animation script
+# MDS - Model Script
+Model script is a file describing what skeleton should be used, what body meshes work with this set of animations and how should the animations be named, how fast they run, what animation is supposed to start after the current one is finished and much more.
-!!! Tip
- The MDS syntax is very simple and scripts can be edited in any text editor. It is, however, easier to work in an editor with a proper syntax highlighting. [Daedalus Language Server](https://github.com/kirides/vscode-daedalus/releases)'s dev branch already merged the MDS grammar for syntax highlighting, we can expect it in the next release.
-
-Model animation script is a file describing what skeleton should be used, what body meshes work with this set of animations and how should the animations be named, how fast they run, what animation is supposed to start after the current one is finished and much more. These files are located in `Gothic\_work\DATA\Anims\` directory.
-
-Whilst the code seems long and terrifying, it is in fact rather simple, and this guide will try to explain it whole.
-
-!!! hint "Don't forget to use the search"
- If you search this file for `t_Yes`, you will get an example of the first type of animation - "standalone"
+!!! Info
+ If you are unfamiliar with the animation naming conventions, please read the [naming conventions](naming.md) article first.
- To play the animation in game you use this console command `play ani t_yes`.
+## Syntax
+The MDS file consists of blocks and commands. Blocks are wrapped in curly braces `{}` and commands are single lines that end with a newline character.
- 
+**Comments**
+Comments start with `//` and continue to the end of the line.
+```dae
+// This is a comment
+```
-## Syntax and keywords
-
-Let us get a quick look at the naming convention to get a basic idea what is going on before we start.
-
-The first letter indicates a type of animation (transition - `t_` - or state - `s_`).
-Then depending on the animation type we have:
+!!! Tip
+ The MDS syntax is very simple and scripts can be edited in any text editor. It is, however, easier to work in an editor with a proper syntax highlighting. [vscode-daedalus](../tools/daedalus_tools/daedalus_language_server.md) extension for Visual Studio Code supports MDS syntax highlighting.
-**Transition animation**
-```
-t_Run_2_Sneak
-```
-Transition animation from the run animation to the sneak animation.
-```
-t_BSANVIL_Stand_2_S0
+## Model
+The whole script is wrapped in the `Model` block. It defines the name of the model this script belongs to.
+```dae
+Model ("MODEL_NAME")
+{
+ //...
+}
```
-Transition animation for the blacksmith's anvil from standing to state 0.
-**State animation**
-```
-s_Run
-```
-State animation for the looping animation.
+### meshAndTree
+This command defines the source file for the model mesh and skeleton in the neutral pose.
+```dae
+meshAndTree ("ASC_NAME" DONT_USE_MESH)
```
-s_BSANVIL_S0
+**Parameters**
+
+`ASC_NAME` - name of the source file
+`DONT_USE_MESH` - optional parameter, if specified the mesh from the source file is not used, only the skeleton. This is used for humans and creatures, since different meshes could use the same skeleton.
+
+### registerMesh
+This command registers a body mesh that can be used with this model (e.g armor or clothing).
+```dae
+registerMesh ("ASC_NAME")
```
-State animation for the blacksmith's anvil and its first state.
+**Parameters**
-### ani
-This is the main command you will be using while defining new animations.
+`ASC_NAME` - name of the source file
-Example:
+### aniEnum
+This block contains all the animations for this model.
```dae
-ani ("t_Yes" 2 "" 0.1 0.1 M. "Hum_Yes_M01.asc" F 1 44)
+aniEnum
+{
+ //...
+}
```
-Syntax:
+
+---
+#### ani
+This is the main command for defining an animation.
```dae
-ani (ANI_NAME LAYER NEXT_ANI BLEND_IN BLEND_OUT FLAGS ASC_NAME ANI_DIR START_FRAME END_FRAME)
+ani ("ANI_NAME" LAYER "NEXT_ANI" BLEND_IN BLEND_OUT FLAGS "ASC_NAME" ANI_DIR START_FRAME END_FRAME)
```
-`ani` - is a keyword, we are defining new animation
+!!! Info
+ Inside the `ani` command [animation events](events.md) could be used.
-Let's describe all the parameters
+!!! Example
+ ```dae
+ ani ("t_Yes" 2 "" 0.1 0.1 M. "Hum_Yes_M01.asc" F 1 44)
+ ```
+**Parameters**
-`ANI_NAME` - animation name, we use it in Daedalus as animation identifier
+`ANI_NAME` - animation name, used in scripts and code as identifier
There is a naming convention, that is recommended and sometimes required to be used.
@@ -87,7 +99,7 @@ If we set it to 0.5, it takes 0.5 seconds for this animation to take full effect
- **F** - the engine ignores height coordinate - doesn't keep the model "glued" to the ground (falling/flying animation)
- **I** - specifies idle animation - breathing, standing with a drawn weapon and moving the weapon
-`ASC_NAME` - name of the source file exported from Blender
+`ASC_NAME` - name of the source file
`ANI_DIR` - direction of the animation
@@ -98,17 +110,32 @@ If we set it to 0.5, it takes 0.5 seconds for this animation to take full effect
`END_FRAME` - on what frame from the source file the animation ends
-### aniAlias
-Generally considered as one of the most useful commands, `aniAlias` is used to create an alias (hard link for UNIX users) for an already defined animation.
+**Additional parameters**
-Example:
-```dae
-aniAlias ("t_Sneak_2_Run" 1 "s_Run" 0.0 0.1 M. "t_Run_2_Sneak" R)
-```
-Syntax:
+Some additional parameters could be specified at the end of the command, these are optional and not used in all animations.
+
+`FPS:XX` - sets the frames per second for this animation
+
+`SPD:XX` - sets the speed multiplier
+
+`CVS:XX` - sets the collision volume scale
+
+!!! Example
+ ```dae
+ ani ("s_Run" 1 "s_Run" 0.0 0.0 M. "Hum_Run_M01.asc" F 1 30 FPS:30 CVS:0.1)
+ ```
+
+---
+#### aniAlias
+Command to create an alias (hard link for UNIX users) for an already defined animation.
```dae
-aniAlias (ANI_NAME LAYER NEXT_ANI BLEND_IN BLEND_OUT FLAGS ALIAS_NAME ANI_DIR)
+aniAlias ("ANI_NAME" LAYER "NEXT_ANI" BLEND_IN BLEND_OUT FLAGS "ALIAS_NAME" ANI_DIR)
```
+!!! Example
+ ```dae
+ aniAlias ("t_Sneak_2_Run" 1 "s_Run" 0.0 0.1 M. "t_Run_2_Sneak" R)
+ ```
+**Parameters**
`ANI_NAME` - name of the new animation
@@ -133,32 +160,162 @@ aniAlias ("t_Sneak_2_Run" 1 "s_Run" 0.0 0.1 M. "t_Run_2_Sneak" R)
```
In this example we are defining `t_Sneak_2_Run` animation and we are specifying that the animation after this one is finished will be `s_Run` and that it is being made by reversing animation `t_Run_2_Sneak` by specifying the `R` flag.
-### aniBlend
-AniBlend is used to define animations that are a result of blending of two animations. This animation is not animated by hand, but it is dynamically generated by the engine during run-time.
+---
+#### aniBlend
+Command to define animations that are a result of blending of two animations. This animation is not animated by hand, but it is dynamically generated by the engine during run-time.
-Example
+Syntax:
```dae
-aniBlend ("t_RunR_2_Run" "s_Run" 0.2 0.2)
+aniBlend ("ANI_NAME" "NEXT_ANI" BLEND_IN BLEND_OUT)
```
-Syntax:
+
+!!! Example
+ ```dae
+ aniBlend ("t_RunR_2_Run" "s_Run" 0.2 0.2)
+ ```
+**Parameters**
+
+`ANI_NAME` - name of the new animation
+
+`NEXT_ANI` - name of the next animations
+
+`BLEND_IN` - time in seconds describing animation blending at the start
+
+`BLEND_OUT` - time in seconds describing animation blending at the end
+
+---
+#### aniComb
+Command that defines an animation that is created by interpolating several animations with an equal number of frames.
```dae
-aniBlend (ANI_NAME NEXT_ANI BLEND_IN BLEND_OUT)
+aniComb ("ANI_NAME" LAYER "NEXT_ANI" BLEND_IN BLEND_OUT FLAGS "ANI_PREFIX" NUM_ANI)
```
+!!! Example
+ ```dae
+ ani ("c_bow_1" 4 "" 0.1 0.1 .. "bow_shoot.asc" F 41 41)
+ ani ("c_bow_2" 4 "" 0.1 0.1 .. "bow_shoot.asc" F 43 43)
+ ani ("c_bow_3" 4 "" 0.1 0.1 .. "bow_shoot.asc" F 47 47)
+ ani ("c_bow_4" 4 "" 0.1 0.1 .. "bow_shoot.asc" F 49 49)
+
+ aniComb ("s_bow_aim" 1 "s_bow_aim" 0.1 0.1 M. "c_bow_" 4)
+ ```
+
+ In this example, `aniComb` creates the `s_bow_aim` animation by combining four preceding `ani` phases (`c_bow_1` to `c_bow_4`). Each phase uses the same source file but different frame ranges.
+
+**Parameters**
`ANI_NAME` - name of the new animation
+`LAYER` - layer the animation is on
+
`NEXT_ANI` - name of the next animations
`BLEND_IN` - time in seconds describing animation blending at the start
`BLEND_OUT` - time in seconds describing animation blending at the end
+`FLAGS` - flags, that describe animation behavior
+
+`ANI_PREFIX` - prefix of the animations to combine
+
+`NUM_ANI` – number of previous `ani` commands to combine
+
+
+---
+#### aniMaxFPS
+Sets the default maximum frame rate for all animations, if not specified, the default value (25 FPS) is used.
+```dae
+aniMaxFPS (FPS_VALUE)
+```
+**Parameters**
+
+`FPS_VALUE` - maximum frames per second
-### aniSync
-Not used in the game.
+---
+#### aniDisable
+Disables an animation, so it is not played by the engine.
+```dae
+aniDisable ("ANI_NAME")
+```
+!!! Bug
+ This command is broken and doesn't work as expected
+**Parameters**
-### aniBatch
-Not used in the game.
+`ANI_NAME` - name of the animation to disable
+
+---
+#### aniBatch
+Command to combine multiple animations into one. When the combined animation is played, all the animations in the batch are played simultaneously. This could be used e.g. to split the upper and lower body animations.
+```dae
+aniBatch ("ANI_NAME")
+{
+ *aniBatch ("BATCH_ANI_NAME")
+ // ...
+}
+```
+!!! Danger
+ This command is not used in the game files and might not work as expected.
+
+!!! Example
+ ```dae
+ aniBatch ("t_1h_slash1")
+ {
+ *aniBatch ("t_1h_slash1_top")
+ *aniBatch ("t_1h_slash1_bot")
+ }
+ ```
+**Parameters**
+
+`ANI_NAME` - name of the new animation
+
+`BATCH_ANI_NAME` - name of the animation to be played in the batch, it must be defined previously in the script
+
+---
+#### aniSync
+Unknown command, never used in the game files.
+```dae
+aniSync ("ANI_NAME" "NEXT_ANI")
+```
+
+## Example ModelScript
+For a better understanding of the MDS syntax, here is a simple example of a human model script.
+
+```dae
+Model ("HuS")
+{
+ meshAndTree ("Hum_Body_Naked0.ASC" DONT_USE_MESH)
+
+ registerMesh ("Hum_Body_Naked0.ASC")
+ registerMesh ("Hum_Body_CookSmith.ASC")
+
+ aniEnum
+ {
+ modelTag ("DEF_HIT_LIMB" "zs_RightHand")
+
+ ani ("s_stand" 1 "s_stand" 0.5 0.5 M. "stand_pause2.asc" F 0 -1)
+ ani ("t_strafe_l" 1 "s_stand" 0.1 0.1 M. "Strafe_Left.asc" F 0 -1)
+
+ aniBlend ("t_stand_2_run" "s_run")
+
+ aniSync ("t_run_2_walk" "s_walk")
+
+ aniAlias ("t_strafe_r" 1 "s_stand" 0.1 0.1 M. "t_strafe_l" R)
+
+ aniBatch ("t_1h_slash1")
+ {
+ *aniBatch ("t_1h_slash1_top")
+
+ *aniBatch ("t_1h_slash1_bot")
+ }
+
+ ani ("t_1h_shield_ready" 5 "" 0.2 0.2 .. "shield_ready.asc" F 0 -1)
+ {
+ *eventSwapMesh(13 "zs_Shield" "zs_LeftArm")
+ }
+ //...
+ }
+
+}
+```
## Animation state machine
More complex animations such as MOBSI animations form a state machine - an animation set.
@@ -214,3 +371,5 @@ stateDiagram-v2
s_S0 --> t_S0_Try
t_S0_Try --> s_S0
```
+
+[^1]: Inspired by the [MDS article](https://worldofplayers.ru/threads/36653/) by VAM.
\ No newline at end of file
diff --git a/docs/zengin/anims/mms.md b/docs/zengin/anims/mms.md
new file mode 100644
index 0000000000..450e497364
--- /dev/null
+++ b/docs/zengin/anims/mms.md
@@ -0,0 +1,75 @@
+---
+title: MMS MorphMeshScript
+---
+
+# MMS - Morph Mesh Script
+MMS files are used to define morph mesh animations. They are similar to MDS files but simpler in structure.
+
+## Syntax
+The MMS file is a plain text file with a simple syntax. It consists of commands and parameters.
+
+**Comments**
+
+Comments start with `//` and continue to the end of the line.
+```dae
+// This is a comment
+```
+
+## morphMesh
+The whole script is wrapped in the `morphMesh` block. It defines the name of the morph mesh this script belongs to.
+```dae
+morphMesh ("MORPH_MESH_NAME")
+```
+!!! Bug
+ This command really does nothing and the morph mesh name is taken from the file name itself, but it is good to have it here for consistency with MDS files.
+
+**Parameters**
+
+`MORPH_MESH_NAME` - name of the morph mesh this script belongs to, usually the same as the `.ASC` file name.
+
+### morphRef
+Defines the reference mesh for this morph mesh. The reference mesh is the base mesh that contains all the vertices that will be morphed during animations.
+```dae
+morphRef ("MORPH_MESH_FILE")
+```
+**Parameters**
+
+`MORPH_MESH_FILE` - name of the source `.ASC` file that contains the reference mesh.
+
+### morphAni
+Defines a morph animation.
+```dae
+morphAni ("ANI_NAME" LAYER BLEND_IN HOLDTIME BLEND_OUT FLAGS "ASC_NAME" START_FRAME END_FRAME SPD)
+```
+
+!!! Example
+ ```dae
+ morphAni ("S_NORMAL" 1 0.1 -1 0.1 L "Bow_Long_01.ASC" 0 0 SPD:25)
+ ```
+
+**Parameters**
+
+`ANI_NAME` - name of the animation
+
+`LAYER` - animation layer, probably unused since morph animations can't be combined
+
+`BLEND_IN` - time in seconds to blend into this animation
+
+`HOLDTIME` - time in seconds to hold the animation after it has finished, `-1` means to hold indefinitely
+
+`BLEND_OUT` - time in seconds to blend out of this animation
+
+`FLAGS` - animation flags, can be a combination of the following:
+
+- **D** - discontinuity, animation frames are selected randomly
+- **L** - loop, animation will loop indefinitely
+- **S** - shape animation, the animation represents a complete mesh shape rather than a relative deformation
+- **R** - reference mesh, the compiler will check if vertices and polygons of the next animations match the reference mesh
+
+`ASC_NAME` - name of the source `.ASC` file that contains the animation frames
+
+`START_FRAME` - starting frame of the animation in the `.ASC` file
+
+`END_FRAME` - ending frame of the animation in the `.ASC` file
+
+`SPD` - frames per second, specified as `SPD:XX`
\ No newline at end of file
diff --git a/docs/zengin/anims/naming.md b/docs/zengin/anims/naming.md
new file mode 100644
index 0000000000..1405b2287d
--- /dev/null
+++ b/docs/zengin/anims/naming.md
@@ -0,0 +1,66 @@
+---
+title: Naming conventions
+---
+# Animation naming conventions
+Animations in ZenGin follow a simple naming convention that helps to identify the purpose of each animation.
+
+## Type
+
+The animation prefix indicates a type of animation.
+
+### State
+`s_` is used for state animation. It is usually a looping animation that represents a character's state (e.g., standing, walking, running).
+```
+s_
+```
+**Examples**
+
+``` title="Running state animation"
+s_Run
+```
+
+``` title="Blacksmith's anvil state 0 animation"
+s_BSANVIL_S0
+```
+
+### Transition
+`t_` is used for transition animations. These animations are played when transitioning from one state to another.
+```
+t__2_
+```
+**Examples**
+
+``` title="Transition from running to sneaking"
+t_Run_2_Sneak
+```
+
+``` title="Transition for blacksmith's anvil standing to state 0"
+t_BSANVIL_Stand_2_S0
+```
+
+### Random
+`r_` is used for random animations. These are non-looping animations that can be played randomly, often used for idle behaviors or actions.
+```
+r_
+```
+**Examples**
+
+``` title="Random scratching head animation"
+r_ScratchHead
+```
+
+
+### Combined
+
+`c_` is used for parts of combined animation that are later combined with [`aniComb`](mds.md#anicomb) in MDS files
+```
+c__
+```
+**Examples**
+
+``` title="Bow aiming"
+c_bow_1
+c_bow_2
+...
+s_bowAim <- final animation
+```
diff --git a/docs/zengin/tools/gothic_sourcer.md b/docs/zengin/tools/gothic_sourcer.md
index 9a73bf27fe..4f0b88740a 100644
--- a/docs/zengin/tools/gothic_sourcer.md
+++ b/docs/zengin/tools/gothic_sourcer.md
@@ -4,3 +4,18 @@ Gothic Sourcer is a multipurpose tool. It can be used to edit Daedalus scripts,
## Download
You can download the latest version of Gothic Sourcer [here](https://github.com/muczc1wek/GothicSourcer/releases/tag/v3.16).
+## Animation Decompiler
+Gothic Sourcer can decompile compiled animations back to the raw ASCII format.
+
+!!! Note
+ If you are unfamiliar with the concepts of animation file formats, please refer to the [Animations](../anims/index.md) section first.
+
+### Skeletal Animation
+To decompile skeletal animations, sourcer needs the `.MDS` or `.MSB` to parse the animation data, and also the corresponding `.MDM`, `.MDH` and `.MAN` files to get information about the model and animations.
+
+Choose **Tools > Decompiler models > Dynamic (MDS or MSB)** and select the file you want to decompile. The decompiled files will be saved in the `Gothic Projects\Anims\asc_` directory, next to the `Gothic Sourcer` directory.
+
+### Morph Mesh Animation
+To decompile morph mesh animations, sourcer needs only the `.MMB` file.
+
+Choose **Tools > Decompiler models > MorphMesh (MMS)** and select the file you want to decompile. The decompiled files will be saved in the `Gothic Projects\Anims\asc_` directory, next to the `Gothic Sourcer` directory.
\ No newline at end of file
diff --git a/tests/test_indentation.py b/tests/test_indentation.py
index 5e20092f60..10eb2476a7 100644
--- a/tests/test_indentation.py
+++ b/tests/test_indentation.py
@@ -32,6 +32,11 @@ def test_admonitions_and_lists(self) -> None:
contents, meta = mkdocs.utils.meta.get_data(source)
+ # If the file only has the meta, then it's not stripped, instead it's the contents
+ maybe_meta_contents: str = contents.strip()
+ if maybe_meta_contents.startswith("---") and maybe_meta_contents.endswith("---"):
+ continue
+
last_line = ""
inside_admonition = False
admonition_valid = False
@@ -62,8 +67,12 @@ def test_admonitions_and_lists(self) -> None:
if line.strip() == "":
inside_list = False
+ comment_ending = line.lstrip(" ").startswith("-->")
+
+ assert_line = not inside_codeblock and not comment_ending and not line.strip() == "---"
+
# TODO rewrite it someday with regex
- if line.startswith(self.list_prefixes) and not inside_codeblock:
+ if line.startswith(self.list_prefixes) and assert_line:
self.assertTrue(
len(line) >= 2,
"List entries must have content\n"
diff --git a/tests/test_localization.py b/tests/test_localization.py
index 4aa60f2d72..fd75eef993 100644
--- a/tests/test_localization.py
+++ b/tests/test_localization.py
@@ -48,7 +48,7 @@ def setUpClass(cls) -> None:
splitted = list(map(str.strip, envar.split(",")))
if splitted[-1].lower() in {"true", "false"} or "'" in splitted[-1]:
- fallback_value = eval(splitted.pop())
+ fallback_value = eval(splitted.pop().title())
if fallback_value is None:
for var in splitted: