fix achievement docs and code

This commit is contained in:
Cal 2024-03-29 17:53:27 -06:00
parent 758ffb80c8
commit b480847582
3 changed files with 45 additions and 22 deletions

View file

@ -2,6 +2,8 @@
A simple, but reasonably comprehensive, system for tracking achievements. Achievements are defined using ordinary Python dicts, reminiscent of the core prototypes system, and while it's expected you'll use it only on Characters or Accounts, they can be tracked for any typeclassed object.
The contrib provides several functions for tracking and accessing achievements, as well as a basic in-game command for viewing achievement status.
## Creating achievements
This achievement system is designed to use ordinary dicts for the achievement data - however, there are certain keys which, if present in the dict, define how the achievement is progressed or completed.
@ -92,14 +94,24 @@ ACHIEVEMENT_CONTRIB_ATTRIBUTE = ("achievement_data", "systems")
The primary mechanism for using the achievements system is the `track_achievements` function. In any actions or functions in your game's mechanics which you might want to track in an achievement, add a call to `track_achievements` to update the achievement progress for that individual.
For example, you might have a collection achievement for buying 10 apples, and a general `buy` command players could use. In your `buy` command, after the purchase is completed, you could add the following line:
Using the "kill 10 rats" example achievement from earlier, you might have some code that triggers when a character is defeated: for the sake of example, we'll pretend we have an `at_defeated` method on the base Object class that gets called when the Object is defeated.
Adding achievement tracking to it could then look something like this:
```python
from contrib.game_systems.achievements import track_achievements
track_achievements(self.caller, category="buy", tracking=obj.name, count=quantity)
from contrib.game_systems.achievements import track_achievements
class Object(ObjectParent, DefaultObject):
# ....
def at_defeated(self, victor):
"""called when this object is defeated in combat"""
# we'll use the "mob_type" tag category as the tracked information for achievements
mob_type = self.tags.get(category="mob_type")
track_achievements(victor, category="defeated", tracking=mob_type, count=1)
```
In this case, `obj` is the fruit that was just purchased, and `quantity` is the amount they bought.
If a player defeats something tagged `rat` with a tag category of `mob_type`, it'd now count towards the rat-killing achievement.
The `track_achievements` function does also return a value: an iterable of keys for any achievements which were newly completed by that update. You can ignore this value, or you can use it to e.g. send a message to the player with their latest achievements.

View file

@ -7,23 +7,27 @@ Achievements are defined as dicts, loosely similar to the prototypes system.
An example of an achievement dict:
EXAMPLE_ACHIEVEMENT = {
"name": "Some Achievement",
"desc": "This is not a real achievement.",
"category": "crafting",
"tracking": "box",
"count": 5,
"prereqs": "ANOTHER_ACHIEVEMENT",
ACHIEVE_DIRE_RATS = {
"name": "Once More, But Bigger",
"desc": "Somehow, normal rats just aren't enough any more.",
"category": "defeat",
"tracking": "dire rat",
"count": 10,
"prereqs": "ACHIEVE_TEN_RATS",
}
The recognized fields for an achievement are:
- key (str): The unique, case-insensitive key identifying this achievement. The variable name is
used by default.
- name (str): The name of the achievement. This is not the key and does not need to be unique.
- desc (str): The longer description of the achievement. Common uses for this would be flavor text
or hints on how to complete it.
- category (str): The type of things this achievement tracks. e.g. visiting 10 locations might have
a category of "post move", or killing 10 rats might have a category of "defeat".
- tracking (str or list): The *specific* thing this achievement tracks. e.g. the above example of
- category (str): The category of conditions which this achievement tracks. It will most likely be
an action and you will most likely specify it based on where you're checking from.
e.g. killing 10 rats might have a category of "defeat", which you'd then check from your code
that runs when a player defeats something.
- tracking (str or list): The specific condition this achievement tracks. e.g. for the above example of
10 rats, the tracking field would be "rat".
- tracking_type: The options here are "sum" and "separate". "sum" means that matching any tracked
item will increase the total. "separate" means all tracked items are counted individually.
@ -37,11 +41,12 @@ To add achievement tracking, put `track_achievements` in your relevant hooks.
Example:
def at_use(self, user, **kwargs):
# track this use for any achievements that are categorized as "use" and are tracking something that matches our key
finished_achievements = track_achievements(user, category="use", tracking=self.key)
def at_defeated(self, victor):
# called when this object is defeated in combat
# we'll use the "mob_type" tag category as the tracked information for achievements
mob_type = self.tags.get(category="mob_type")
track_achievements(victor, category="defeated", tracking=mob_type, count=1)
Despite the example, it's likely to be more useful to reference a tag than the object's key.
"""
from collections import Counter
@ -86,7 +91,7 @@ def _read_player_data(achiever):
"""
if data := achiever.attributes.get(_ATTR_KEY, default={}, category=_ATTR_CAT):
# detach the data from the db
data.deserialize()
data = data.deserialize()
# return the data
return data

View file

@ -62,7 +62,9 @@ class TestAchievements(BaseEvenniaTest):
self.assertEqual(self.char1.db.achievements["COUNTING_ACHIEVE"]["progress"], 2)
# also verify that `get_achievement_progress` returns the correct data
self.assertEqual(achievements.get_achievement_progress(self.char1, "COUNTING_ACHIEVE"), {"progress": 2})
self.assertEqual(
achievements.get_achievement_progress(self.char1, "COUNTING_ACHIEVE"), {"progress": 2}
)
@patch(
"evennia.contrib.game_systems.achievements.achievements._ACHIEVEMENT_DATA",
@ -72,7 +74,9 @@ class TestAchievements(BaseEvenniaTest):
"""verify progress is not counted on achievements with unmet prerequisites"""
achievements.track_achievements(self.char1, "get", "thing")
# this should mark progress on COUNTING_ACHIEVE, but NOT on COUNTING_TWO
self.assertEqual(achievements.get_achievement_progress(self.char1, "COUNTING_ACHIEVE"), {"progress": 1})
self.assertEqual(
achievements.get_achievement_progress(self.char1, "COUNTING_ACHIEVE"), {"progress": 1}
)
self.assertEqual(achievements.get_achievement_progress(self.char1, "COUNTING_TWO"), {})
# now we complete COUNTING_ACHIEVE...
@ -81,7 +85,9 @@ class TestAchievements(BaseEvenniaTest):
)
# and track again to progress COUNTING_TWO
achievements.track_achievements(self.char1, "get", "thing")
self.assertEqual(achievements.get_achievement_progress(self.char1, "COUNTING_TWO"), {"progress": 1})
self.assertEqual(
achievements.get_achievement_progress(self.char1, "COUNTING_TWO"), {"progress": 1}
)
@patch(
"evennia.contrib.game_systems.achievements.achievements._ACHIEVEMENT_DATA",