Fix build help
Plant fixes
Bug fixes, enable number with work
Web based MUD with a focus on economy, willpower and control.
Deployed here: stuff.kyleperik.com/relic
Checkout the progress and roadmap here: ticket tracker
Check how to deploy for yourself here: docs/deploy.md
The idea of the game is inspired by Lord of the Rings, and the mechanics are based on several games I've played including Offword Trading Company, Minecraft, and One Hour One Life
I put my raw notes on my initial plan here: docs/spec.md.
Install:
Make sure the docker daemon is running, should just need to turn on Docker Desktop.
Clone this repo and open the cloned repo in Visual Studio Code.
Visual Studio Code should prompt you to re-open the workspace in a container. If not, just hit F1 and type in: Dev Containers: Rebuild and Reopen in Container
and hit enter.
After the devcontainer has been opened, press F5 to turn on Relic!
sr.ht requires SSH keys to push changes, follow this tutorial for setting up SSH keys on your sr.ht account. Then the key will need to be added to your SSH Agent, so the devcontainer can access it. Follow this tutorial (NOTE: this is a microsoft tutorial and they tell you to copy your github key, you probably named your private key file something else, so look for that file in $HOME/.ssh/
)
First make sure you have a version of python 3.10
or greater.
Also make sure you have sqlite3 installed.
First, clone this repo.
Then initialize the database by running each of the migrations
python -m relic.scripts.init_db
Create a virtual environment (pip install virtualenv
):
virtualenv .venv
Activate:
. .venv/bin/activate
Install requirements
pip install -r requirements.txt
Run the server
flask --app relic/main.py --debug run -h 0.0.0.0 -p 8080
It should be running locally on port 8080: http://127.0.0.1:8080
NOTE: As time goes on, changes can be made to event processing, invalidating the cache. If you're running into errors locally after a pulling from master, try hitting
GET /regen_cache
If you have docker installed, you can also run the webserver that way.
docker compose up
You can fork the project and sent me patchsets, or reach out to me for write access.
Please make sure to run make lint
to make sure everything is neat prior to committing.
You can do cp pre-commit .git/hooks/; chmod +x .git/hooks/pre-commit
to enable a git pre-commit hook to disable committing code which hasn't been linted.
Run make lint-fix
to automatically run black and isort over your codebase.
Checkout this video on how the state management system.
This project is made up of a single backend written with flask
for a webserver
and sqlite3
for the db.
The frontend is very basic with just some raw html and js and api calls and vanilla DOM manipulation.
The backend consists mainly of views that interact with "commands" and "actions".
The general flow is as follows
action sent from the ui ("use forest")
-> actions generate a message and commands ("used forest", [-1wp, +10g, ...])
-> commands are recorded and change the game state
-> frontend gets updated with new state
Actions are the input sent from the text box in the UI.
Most of the time, these actions are sent to POST /action
(with action_type and args)
This data is passed to perform_action
in service.py
to be processed, then to the
action logic of the action in action/
.
Here it finds any action registered with the @action
decorator, and generates the
message and commands within an ActionResult
.
The function must be in the format (args: str, state: State) -> ActionResult
, args
being the rest of the string, and state
being the current state of the game.
ActionResult
contains a message and optionally, "commands" to
mutate state.
From here, any commands are then recorded and processed in the future to generate state as I'll talk about in the next section.
aka events
The game state can always be regenerated from the event logs, which is the source of truth.
The purpose of commands is to mutate state. This materializes in a set of functions in a similar way to how actions are registered.
Use the decorator
@command(name, kind)
to register a new command (also make sure to add one to
commands
in the frontend as well). Commands are pretty simple as they take in
one argument which can be any json serializable type, an entity_id, and the current
state. They return nothing, as they only mutate state.
One special thing about commands is that they each have a scope. This currently means commands can be specific to one player or one area.
The kind
which a command is registered
must correspond with either of these two player
or area
. This is for the
purpose of allowing partial state aggregates, for example, if your player is in an
area, they only need to receieve updates for that specific area. These commands are also
stored in different tables player_events
and area_events
.
On page load, the frontend first hits /init
which will initialize with a new player
if needed in a random area. It recieves a full state load, including only state from the player's "memory".
The frontend is capable of syncronous multiplayer, because it polls on an interval for
new changes. To do this, for each entity type/id, it stores the latest event. Then it
calls /head
to see if the latest events
matches with the current ones. If not, it will get update state which have
happened since the recorded event with /player/current
or /area/current
.
The frontend will then process any events to update the local state. Note currently polling is only done for areas at the moment, because player entities should not be changed by other players at the moment.