Skip to content

Technical Details

cyberrumor edited this page Sep 27, 2024 · 2 revisions

Project Vision

Ammo loosely follows the model/view/controller pattern, though the model is represented as class attributes of the controller. On startup, ammo scans the game directory and mod directory to discover which mods are configured. It reads the game's plugins.txt to discover plugin load order, and ~/.local/share/ammo/<game>/ammo.conf to discover mod load order. This data is populated into attributes for a class known as a controller. Those attributes represent the model, and the methods of the controller manipulate it.

A controller is nested within a wrapper class responsible for the view. The view class parses public methods of the controller as well as things like type hints and doc strings to pragmatically generate a help menu and expose those methods to users as interactive CLI commands.

When these commands are executed, the state of the controller's attributes change. This could be something like changing the mod load order, deactivating a plugin, etc. These changes are in-memory and do not persist until a user executes a command dedicated to persisting that temporary state (historically commit) into storage.

This persistence is achieved through writing ammo.conf, plugins.txt, and creating symlinks in the game directory that point to the mod files.

This separation of responsibilities allows the view class to be recycled for any sort of interactive CLI that is necessary to expose, and has the minimum viable amount of abstraction. It allows an interface session for a controller to be nested within the interface session of another controller. It also allows easily testing the controller without requiring cumbersome stdin/stdout tests.

Ammo installs mods as symlinks because they're fast to manipulate and they provide advantages over using a database. In the case of ammo, the database is essentially just the symlinks, which is the same resource used by the game. Consequentially, there is minimal opportunity for the state to descynchronize from reality.

Symlinks also have an advantage over virtual filesystems. Since a symlink is transparent to typical consumers like games or tools, utilizing symlinks instead of a virtual filesystem means ammo doesn't need to be responsible for launching modding tools. Tools like xedit, LOOT, or dyndolod don't need special handling (like exposing a virtual filesystem to them) in order for them to function.

Ammo is simple because it has a narrow scope: manage mods and plugins via CLI using standard imports only.

Ammo is not a download manager or a game launcher, and does not integrate with external tools. Rather, external tools are able to be used in conjunction with ammo without requiring that integration. Tools like LOOT can simply run in-place. Tools like xedit can simply set their output folder to ~/.local/share/ammo/<game>/mods/overwrite.

Contributing

Fork the repository, make and commit changes on your local fork, then open a PR. Please format changes with ruff or black.

Testing

To test ammo, create and activate a python virtual environment, install requirements, then install ammo. Use pytest to execute tests.

cd /path/to/ammo
python3 -m venv .venv
. .venv/bin/activate
pip3 install -r requirements.txt
pip3 install .
pytest tests/

Tips and Tricks

It may be useful in your iterations to automate UI input before you've written tests. I find the easiest way to do this is with this sort of strategy:

(echo "command1 arg1"; echo "command2 arg1") | ammo

If you need to recreate a complex set of initial steps then supply manual input, you can use input redirection:

(echo "instruction1"; echo "instruction2"; cat <&0) | ammo
Clone this wiki locally