Advanced Usage#
This is a hodgepodge of circleguard features not mentioned in other parts of the documentation. This page is written at a high level and is intended for developers who know what they are doing, and are just looking for a page listing all the things they can do, or are looking to learn more about circleguard’s internals.
Retrieve a Beatmap#
To retrieve a beatmap from a replay, call cg.beatmap()
. This returns None
if the replay cannot determine
what beatmap it was set on, and a slider.Beatmap
otherwise.
Generate a Frametime Graph#
To generate a frametime graph, call cg.frametime_graph()
. This is the same method that
circleguard calls to generate its frametime graphs.
You must have matplotlib installed to use this method.
Utilities#
convert_statistic#
convert_statistic()
converts a gameplay statistic such as unstable rate to its converted or unconverted form, given the mods
a replay was played with.
>>> convert_statistic(16, Mod.HDDT, to="cv")
10.666666666666666
>>> convert_statistic(16, Mod.HDDT, to="ucv")
24
>>> convert_statistic(16, Mod.NM, to="cv")
16
A “statistic” in this context is anything modified by the game speed. So frametime and unstable rate are both “statistics” that come in either a converted or unconverted form.
replay_pairs#
replay_pairs()
generates the pairs of replays which should be compared to cover all cases of replay stealing among the passed
sets of replays. See its documentation for more details.
fuzzy_mods#
fuzzy_mods()
returns all the mod combinations with the given required and optional mods:
>>> fuzzy_mods(Mod.HD, [Mod.DT])
[HD, HDDT]
>>> fuzzy_mods(Mod.HD, [Mod.EZ, Mod.DT])
[HD, HDDT, HDEZ, HDDTEZ]
>>> fuzzy_mods(Mod.NM, [Mod.EZ, Mod.DT])
[NM, DT, EZ, DTEZ]
See its documentation for more details.
order#
order()
returns 2-tuple of the given replays, with the replay that was played earlier first and the replay that was played later second.
Subclassing Replay#
Subclassing Replay
can be useful if you want to pull replay data from a source that circleguard doesn’t support, or if
you want to customize the replay data processing. One common use case I can think of is providing a ReplayAkatsuki
(or
(some similar class for another private server) which uses a private server’s api to create a replay.
When sublcassing Replay
, you must provide an implementation of the load
method. You will also
want to consider overriding the beatmap
and map_available
methods, especially if your replay subclass
could be played on beatmaps that aren’t available from the official osu! servers, or if you want to customize
where the beatmap gets loaded from. However, overriding these methods is not necessary. Only the load
method must be provided.
load#
The load
method is what circleguard calls to load the replay data of the replay. This method is responsible for retrieving the
replay’s replay data from whatever data source it wants (an api, a replay file, a database, etc). Once it does so, it must call
self._process_replay_data
with the loaded replay data and set self.loaded
to True
.
Note
One philosophy of circleguard is that Replay
objects are pseudo lazy loaded. That is, they are cheap to
instantiate and only incur a cost when they need to be loaded. This is why we require that loading logic goes into
its own load
method instead of happening on instantiation.
load
takes two arguments: loader
(a Loader
instance) and cache
(a boolean). loader
provides you with access to
the api, should your replay need it. cache
is True
if the replay should be cached to the database once loaded, and
False
otherwise. You do not have to respect cache
, even if True
, if you do not want to (or cannot) implement caching
for your replay. For instance, we do not currently cache ReplayPath
instances under any circumstances.
In order to facilitate replays which can be loaded without api access, loader
may be null. If your replay needs api access
to load itself, and is passed a null loader
, raise an error:
if not loader:
raise ValueError("A Map cannot be info loaded without api "
"access")
If your replay does not need api access, you may safely ignore the loader
argument.
The replay data that you pass to _process_replay_data
must be a list of
osrparse.ReplayEventOsu objects. You should convert your replay data
to instances of this class yourself if your data does not come as osrparse objects by default.
If your replay cannot load its replay data for whatever reason, pass None
to _process_replay_data
. Do not pass an empty list,
as this has a different meaning (that the replay data is empty instead of unretrievable).
There are several attributes which you should always set if you can retrieve them. Some circleguard features will not work without these -
for instance, replays without a timestamp cannot be ordered, and replays without a map id cannot have their beatmap retrieved (unless you
provide an alternative beatmap
method; more on this later). You can view a full list of these attributes in the Replay
documentation.
For instance, ReplayMap
makes api calls during its load
method, which tells it the following attributes: timestamp
, username
,
mods
, and replay_id
. So ReplayMap
sets these attributes inside its load
method (alongside game_version
, which it
can estimate from the timestamp):
self.timestamp = info.timestamp
# estimate version with timestamp, this is only accurate if the user
# keeps their game up to date
self.game_version = GameVersion.from_datetime(self.timestamp,
concrete=False)
self.username = info.username
self.mods = info.mods
self.replay_id = info.replay_id
Your replay subclass should set as many of these attributes as your data source can provide.
beatmap#
The purpose of this method is to return a slider.Beatmap
representing the beatmap that this replay was played on.
Some circleguard methods require a slider.Beatmap
to work properly (a currently exhaustive list is cg.ur()
, cg.hits()
, and
cg.judgments()
), or a beatmap can be optionally used to improve some statistical calculations. Whenever a beatmap is
required, Circleguard
calls replay.Beatmap()
, passing its slider.Library
instance.
The default implementation of beatmap
is to use the replay’s map id and ask the passed slider.Library
to retrieve the beatmap
with that id. If the replay does not provide a map id, the replay will be unable to provide a beatmap by default, since it doesn’t
know what map it was played on.
To override this behavior, override beatmap
and return either a slider.Beatmap
object, or None
if a beatmap could not be retrieved.
You can can query the passed library for the relevant beatmap if you find it useful, or you can ignore it in favor of retrieving the
beatmap another way. However, remember that at the end of the day you must return a slider.Beatmap
. See slider’s documentation
for more details on creating beatmaps from scratch, or from an osu! file, if you need them.
map_available#
Return True
if the replay could retrieve its beatmap if asked, and false otherwise. This is intended to be an inexpensive alternative
to calling beatmap
and checking for a null response.
Subclassing ReplayContainer#
To add a new ReplayContainer
, you need only provide the load_info
and all_replays
methods.
Unless you know exactly what you’re doing, you likely do not want to override load
on a ReplayContainer
. The default
implementation should be exactly what you want.
load_info#
When called, the ReplayContainer
should populate itself with Replay
instances. It should only create them and not load them; they
will be loaded when ReplayContainer#load
is called. A Loader
instance is passed to this method to provide access to the api.
all_replays#
A list of all replays in this ReplayContainer
. This is provided to allow for arbitrarily complex storages of replays. For instance,
there might be a replay container which partitions its replay list into two sets, and stores those as separate attibutes. Such a
replay container would return the union of those sets in this method.
Note
If the above instructions for subclassing Replay
and ReplayContainer
are unclear, you should look at the
source code for examples. Every default Replay
and ReplayContainer
subclass is well abstracted and documented in the source
code, and may be a clearer guide to follow than the above examples. See
https://github.com/circleguard/circlecore/blob/master/circleguard/loadables.py for the file containing these classes.