-
Notifications
You must be signed in to change notification settings - Fork 6
Technical Details
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
.
Fork the repository, make and commit changes on your local fork, then open a PR. Please format changes with ruff or black.
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/
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