More work on the arcaea post
This commit is contained in:
parent
3206bd5948
commit
bc3d9f79ff
|
@ -24,20 +24,19 @@ A discussion on pretty much every aspect of the mobile rhythm game "Arcaea".
|
|||
The game judges the way the player hits (or fails to do so) every note by quantizing the inputs into [a few judgements](https://arcaea.fandom.com/wiki/Scoring): a PURE judgement is awarded within a 50ms timing window, the more lenient FAR judgement has twice as much (100ms), with all the other notes being marked as LOST. Moreover, an optional tighter (25ms) timing window awards the MAX PURE (usually referred to as "shiny pure" by the community) judgement.
|
||||
|
||||
{ title="Lagrange's extras: Ghost tapping" character="lagrange" }
|
||||
::: long-aside
|
||||
The game only awards judgements for notes in the chart. That is, tapping while no notes are nearby will not influence the score in any way! This turns out to be quite useful, leading to what is referred to as "ghost tapping", which can be a useful way to keep the rhythm or have fun with the chart. An example of this in action can be seen in [this clip](https://youtube.com/clip/UgkxY9g3nlFz8ZnPDTcn4yIofGdrFIxP3mYz?si=yI6Vs-_qpztONwtu) of [@gba553](https://www.youtube.com/@Gba553) timing the final pair of tricky notes in [ℵ~0~](https://arcaea.fandom.com/wiki/Aleph-0).
|
||||
::: char-aside
|
||||
The game only awards judgements for notes in the chart. That is, tapping while no notes are nearby will not influence the score in any way! This turns out to be quite useful, leading to what is referred to as "ghost tapping", which can be a useful way to keep the rhythm or have fun with the chart. An example of this in action can be seen in [this clip][A clip of @gba553 PMing ℵ~0~] of [@gba553](https://www.youtube.com/@Gba553) timing the final pair of tricky notes in [ℵ~0~][The ℵ~0~ chart].
|
||||
|
||||
[A clip of @gba553 PMing ℵ~0~]: https://youtube.com/clip/UgkxY9g3nlFz8ZnPDTcn4yIofGdrFIxP3mYz?si=yI6Vs-_qpztONwtu
|
||||
[The ℵ~0~ chart]: https://arcaea.fandom.com/wiki/Aleph-0
|
||||
:::
|
||||
|
||||
The score is then calculated using a 2:1 PURE to FAR ratio (that is, every PURE is awarded 2 points and every FAR is awarded 1), the result of which is scaled up so $`10,000,000` is the maximum possible score. Finally, the game awards one additional point for each MAX PURE, which is why perfect scores are usually a bit over $`10,000,000`.
|
||||
The score is then calculated using a 2:1 PURE to FAR ratio (that is, every PURE is awarded 2 points and every FAR is awarded 1), the result of which is scaled up so $`10,000,000` is the maximum possible score. Finally, the game awards one additional point for each MAX PURE, which is why perfect scores are usually a bit over $`10,000,000`. This exact form of scoring is present in many other rhythm games. For instance, [Sound Voltex][] uses the exact same judgement ratio!
|
||||
|
||||
::: aside
|
||||
This exact form of scoring is present in many other rhythm games. For instance, [sound voltex](https://en.wikipedia.org/wiki/Sound_Voltex) uses the exact same judgement ratio!
|
||||
:::
|
||||
[Sound Voltex]: https://en.wikipedia.org/wiki/Sound_Voltex
|
||||
|
||||
A play with no LOST judgements is called a "full recall" (also known as a "full combo" in other games). Furthermore, a play with no FAR judgements is called a "pure memory" (also known as an "all perfect" in other games, and commonly abbreviated as a "PM"). Finally, a play where every note was hit perfectly (that is, one where everything is a MAX PURE) is called a "max pure memory" (commonly abbreviated as "MPM" by the community).
|
||||
|
||||
While full recalls and max-PMs are certainly celebrated by players and the larger community alike, PMs are usually the sweet spot between accurate play and fun which many Arcaea players strive for. This is usually the case because the game hardly rewards (if acknowledge at all) plays that are better than a PM (this will become obvious when we discuss the game's rating system).
|
||||
|
||||
{title="Lagrange's extras: The scoring formula" character="lagrange"}
|
||||
::: char-aside
|
||||
Let's write the score formula in a nice, closed form!
|
||||
|
@ -47,9 +46,11 @@ Let $`m`, $`p`, $`f` and $`l` denote the amount of MAX PURE, PURE, FAR and LOST
|
|||
$$`m + \left\lfloor (2(m + p) + f) \frac{10'000'000}{2(m + p + f + l)} \right\rfloor`
|
||||
:::
|
||||
|
||||
While full recalls and max-PMs are certainly celebrated by players and the larger community alike, PMs are usually the sweet spot between accurate play and fun which many Arcaea players strive for. This is usually the case because the game hardly rewards (if acknowledge at all) plays that are better than a PM (this will become obvious when we discuss the game's rating system).
|
||||
|
||||
{title="Lagrange's extras: ζ-scoring" character="lagrange"}
|
||||
::: long-aside
|
||||
UWAAA, but what if we wanted MAX PURE notes to have a more major contribution to the score? For one, we could start by giving them their own place in the scoring ratio. What would a good ratio look like? A naive approach idea would be to keep the same rate of growth and go with a ratio of 4\:2\:1 for MAX PURE to PURE to FAR. Sadly, issues arise because this can lead to PMs possibly producing terrible scores — it's too big of a departure from the original formula. It turns out the aforementioned [sound voltex](https://en.wikipedia.org/wiki/Sound_Voltex) has already figured out a solution with their optional "EX-scoring" system, which uses a ratio of 5\:4\:2, thus awarding 1.25x the normal points for a MAX PURE.
|
||||
UWAAA, but what if we wanted MAX PURE notes to have a more major contribution to the score? For one, we could start by giving them their own place in the scoring ratio. What would a good ratio look like? A naive approach idea would be to keep the same rate of growth and go with a ratio of 4\:2\:1 for MAX PURE to PURE to FAR. Sadly, issues arise because this can lead to PMs possibly producing terrible scores — it's too big of a departure from the original formula. It turns out the aforementioned [Sound Voltex][] has already figured out a solution with their optional "EX-scoring" system, which uses a ratio of 5\:4\:2, thus awarding 1.25x the normal points for a MAX PURE.
|
||||
|
||||
Calling this "EX-scoring" in the context of Arcaea would be confusing (EX being an in-game grade and all), hence I decided to call the Arcaea equivalent of the system "ζ-scoring". In particular, we can compute the ζ-score by only knowing the base score and the number of notes in the chart (call it $`n`) as follows:
|
||||
|
||||
|
@ -64,7 +65,10 @@ With a bit of care put into working around the floor function in the actual scor
|
|||
#### Why arcaea's scoring rocks
|
||||
{% [[[ %}
|
||||
|
||||
From the description I've written up above, it's pretty clear that Arcaea's scoring system only cares about one thing — accuracy. In contrast, certain games (notably [OSU!](https://osu.ppy.sh/), [Phigros](https://phigros.fandom.com/wiki/Phigros_Wiki), and more) take a different approach, rewarding additional points when players hit a series of multiple notes in a row, with said bonus usually scaling with the length of the so-called combo. This bonus could for example consist of a mulitplier (ala OSU!), or a linear score increase (ala Phigros). This approach does have certain advantages:
|
||||
From the description I've written up above, it's pretty clear that Arcaea's scoring system only cares about one thing — accuracy. In contrast, certain games (notably [OSU!][], [Phigros][], and more) take a different approach, rewarding additional points when players hit a series of multiple notes in a row, with said bonus usually scaling with the length of the so-called combo. This bonus could for example consist of a mulitplier (ala OSU!), or a linear score increase (ala Phigros). This approach does have certain advantages:
|
||||
|
||||
[OSU!]: https://osu.ppy.sh/
|
||||
[Phigros]: https://phigros.fandom.com/wiki/Phigros_Wiki
|
||||
|
||||
1. Live competitions for games like OSU! are extremely tense to watch, due to every drop in combo having a huge impact on the standings.
|
||||
2. Combo-based scoring can help certain styles of charting — for instance, more suspense can be built towards the end of a song, added emphasis can be put on certain short-but-difficult sections, etc.
|
||||
|
@ -82,7 +86,9 @@ Ok, sorry, I will calm down now. Back to the analytic style I go. I think combo-
|
|||
|
||||
4. Combo based scoring encourages bad practices, like learning to cheese certain patterns by slightly sacrificing accuracy in favour of keeping the combo alive. In contrast, accuracy-based scoring encourages players to find unique ways to handle certain patterns without compromising on accuracy (for example, funky multi-finger playstyles in Arcaea).
|
||||
|
||||
5. Combo-based scoring reduces the number of intermediate goals the player can set for themselves. When I was first getting into Arcaea, I set a goal of full-recalling a FTR 7 chart. Not long after, I managed to do that on ["Brand new world"](https://www.youtube.com/watch?v=as0dIU6CCNk). While I was happy with my achievement, my score was barely an AA (that is, my accuracy was slightly higher than 95%)!
|
||||
5. Combo-based scoring reduces the number of intermediate goals the player can set for themselves. When I was first getting into Arcaea, I set a goal of full-recalling a FTR 7 chart. Not long after, I managed to do that on ["Brand new world"][A PM of "Brand new world"]. While I was happy with my achievement, my score was barely an AA (that is, my accuracy was slightly higher than 95%)!
|
||||
|
||||
[A PM of "Brand new world"]: https://www.youtube.com/watch?v=as0dIU6CCNk
|
||||
|
||||
In a game like OSU!, I could either eye the harder goal of PMing the chart (which, at the time, I was far from skilled enough to do), or start ignoring the score and looking at accuracy instead — greatly worsening the play experience, with things like the game's UI, grading system, and rating computations all revolving around scores.
|
||||
|
||||
|
@ -90,6 +96,12 @@ Ok, sorry, I will calm down now. Back to the analytic style I go. I think combo-
|
|||
|
||||
Overall, I think Arcaea's (and by extension, many other similar games') scoring system is awesome, and activey contributes to the experience in a positive manner.
|
||||
|
||||
::: aside
|
||||
_On [2024-10-28]{ role="date" }, the [OSU!](https://osu.ppy.sh/) team [announced][pp-combo-removal] the removal of the combo element of their PP system._
|
||||
|
||||
[pp-combo-removal]: https://osu.ppy.sh/home/news/2024-10-28-performance-points-star-rating-updates
|
||||
:::
|
||||
|
||||
{% ]]] %}
|
||||
|
||||
### Expressivity
|
||||
|
@ -101,7 +113,18 @@ Overall, I think Arcaea's (and by extension, many other similar games') scoring
|
|||
{% ]]] %}
|
||||
|
||||
### Course mode
|
||||
- it's cool, wish there were more of them
|
||||
|
||||
Arcaea's [course mode][Course mode on the Arcaea wiki] functions similarly to [dan courses in Maimai][Dan courses in Maimai] or [the Skill Analyzer mode in Sound Voltex][Sound Voltex Skill Analyzer]. Each course consists of four distinct charts the player must clear in a row. The life gauge is set to hard mode, and life does not reset between levels.
|
||||
|
||||
[Course mode on the Arcaea wiki]: https://arcaea.fandom.com/wiki/Course_Mode
|
||||
[Dan courses in Maimai]: https://silentblue.remywiki.com/maimai:FiNALE_Course
|
||||
[Sound Voltex Skill Analyzer]: https://remywiki.com/SKILL_ANALYZER
|
||||
|
||||
The reward for completing a course consists of a negligible amount of fragments, and a cool-looking profile banner. On [2024-05-21]{ role="date" }, Lowiro announced a new set of subscription benefits for [Arcaea Online](#arc-online), including a new profile banner being unlocked every month. As a result, the course mode banners are now less common throughout the community, although they're still there.
|
||||
|
||||
I personally really like the course mode system, although it feels very underdeveloped. There's been no new courses added since the mode got introduced in 2022, and there's little incentive to replay courses once they've been cleared. Moreover, the steep stamina cost means players are likely to only try playing through a course once they're confident they can easily clear every chart involved, removing any element of suspense. Unlike Sound Voltex's Skill Analyzer, there's only one course for each difficulty, which is not helping in the variety department.
|
||||
|
||||
One nice thing about the mode is that players can use warped shards to play through a course without owning all the songs involved. While nice in theory, warped shards are quite grindy to unlock, and come in very limited quantities, so this isn't very big in the grand scheme of things.
|
||||
|
||||
### Unlocks and progression
|
||||
- Why world mode grindy:(
|
||||
|
@ -135,6 +158,7 @@ Overall, I think Arcaea's (and by extension, many other similar games') scoring
|
|||
- some people use spreadsheets
|
||||
- the legend says there's still working bots out there, but who knows :)
|
||||
|
||||
{ #arc-online }
|
||||
#### Arcaea Online — an annoying business practice
|
||||
- value is better than at launch
|
||||
- a lot of the provided value is a cure for a self-inflicted poison
|
||||
|
@ -180,16 +204,18 @@ Overall, I think Arcaea's (and by extension, many other similar games') scoring
|
|||
- [guest paragraph by dyuan]
|
||||
|
||||
### The slipping issue
|
||||
- anti slip mats
|
||||
- Fatal's toilet paper solution
|
||||
- some people are using finger gloves (you can get them in bulk for cheap I think)
|
||||
{% [[[ The slipping issue %}
|
||||
|
||||
Every Arcaea player (barring thumb gods) will eventually have to deal with their device slipping during play — it's inevitable once you get to a certain level. Tokaku demonstrated this nicely in her [Finger Sleeve review video][Tokaku's Finger Sleeve video]. Fortunately, solving this is not super expensive. I personally use an anti-slip mat. I won't provide any product links, but you should be able to find those for cheap in a lot of places. Alternatively, you can go for the aforementioned finger sleeves — they can be bought in bulk for pretty cheap (do not waste your money on Razer's overpriced sets). Some people have had luck with covers made of certain materials, although I haven't tested this myself. Finally, if you're really desperate, you can try my friend Fatal's solution: putting toilet paper under your device!
|
||||
|
||||
[Tokaku's Finger Sleeve video]: https://www.youtube.com/watch?app=desktop&v=31V1h3ZiXag
|
||||
{% ]]] %}
|
||||
|
||||
### Sliding across large screens
|
||||
- finger gloves fix this I guess but ion use them
|
||||
- greasy devices aleviate this a bit, but lead my fingertip skin to tear a lot
|
||||
- certain kinds of matte screen protectors fix the issue!
|
||||
|
||||
|
||||
{ title="Arcaea and posture" character="lagrange" }
|
||||
::: aside
|
||||
Not sure what the proper posture is, but I've found myself doing much better when putting some pillows on my chair so I sit slightly higher
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Articles
|
||||
# Echoes
|
||||
|
||||
> "Remnants of the One who once dwelled within the Silver of the Sky. They now wander endlessly through the mists of the heavens, at times drawn to lost vessels who seek to hear them. Thence, thou with this knowledge are yet to attain the full knowledge of the Plan, but worry not — the World shall linger much longer still."
|
||||
> _"Remnants of the One who once dwelled within the Silver of the Sky. They now wander endlessly through the mists of the heavens, at times drawn to lost vessels who seek to hear them. Thence, thou with this knowledge are yet to attain the full knowledge of the Plan, but worry not — the World shall linger much longer still."_
|
||||
|
||||
This page contains a list of all my long-form blog posts.
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ h6 {
|
|||
display: inline-block;
|
||||
text-decoration: none;
|
||||
/* Note: I need to check whether this only aligns things better with the font I use */
|
||||
transform: translateY(-2px);
|
||||
translate: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ math[display="block"] {
|
|||
}
|
||||
|
||||
.aside {
|
||||
background: #f4daf7;
|
||||
background: #faebff;
|
||||
border-radius: 3px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
|
55
src/html.rs
55
src/html.rs
|
@ -3,7 +3,9 @@ use std::fmt::Display;
|
|||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
use chrono::DateTime;
|
||||
use chrono::NaiveDate;
|
||||
use chrono::TimeZone;
|
||||
use jotdown::Alignment;
|
||||
use jotdown::AttributeValue;
|
||||
|
@ -19,6 +21,7 @@ use tree_sitter_highlight::HighlightConfiguration;
|
|||
use tree_sitter_highlight::HighlightEvent;
|
||||
use tree_sitter_highlight::Highlighter;
|
||||
|
||||
use crate::metadata::has_role;
|
||||
use crate::metadata::PageMetadata;
|
||||
use crate::metadata::PageRoute;
|
||||
use crate::template;
|
||||
|
@ -58,6 +61,8 @@ enum State<'s> {
|
|||
Aside(TemplateRenderer<'s>),
|
||||
Article(TemplateRenderer<'s>),
|
||||
Footnote(Vec<jotdown::Event<'s>>),
|
||||
Datetime(String),
|
||||
Date(String),
|
||||
}
|
||||
|
||||
impl<'s> Writer<'s> {
|
||||
|
@ -214,7 +219,7 @@ impl<'s> Writer<'s> {
|
|||
// {{{ Link
|
||||
Container::Link(dst, ty) => {
|
||||
if matches!(ty, LinkType::Span(SpanLinkType::Unresolved)) {
|
||||
out.write_str("<a>")?;
|
||||
bail!("Unresolved url {dst:?}")
|
||||
} else {
|
||||
let prefix = if matches!(ty, LinkType::Email) {
|
||||
"mailto:"
|
||||
|
@ -316,10 +321,7 @@ impl<'s> Writer<'s> {
|
|||
// }}}
|
||||
// {{{ Div
|
||||
Container::Div { class } => {
|
||||
if attrs
|
||||
.get_value("role")
|
||||
.map_or(false, |role| format!("{role}") == "description")
|
||||
{
|
||||
if has_role(attrs, "description") {
|
||||
self.states.push(State::Ignore);
|
||||
} else {
|
||||
write!(out, "<div{}>", Attr("class", class))?;
|
||||
|
@ -348,6 +350,7 @@ impl<'s> Writer<'s> {
|
|||
out.write_str(r#"<img alt=""#)?;
|
||||
}
|
||||
Container::Footnote { .. } => self.states.push(State::Footnote(Vec::new())),
|
||||
Container::LinkDefinition { .. } => self.states.push(State::Ignore),
|
||||
Container::Blockquote => out.write_str("<blockquote>")?,
|
||||
Container::ListItem { .. } => out.write_str("<li>")?,
|
||||
Container::DescriptionList => out.write_str("<dl>")?,
|
||||
|
@ -356,7 +359,15 @@ impl<'s> Writer<'s> {
|
|||
Container::TableRow { .. } => out.write_str("<tr>")?,
|
||||
Container::Caption => out.write_str("<caption>")?,
|
||||
Container::DescriptionTerm => out.write_str("<dt>")?,
|
||||
Container::Span => out.write_str("<span>")?,
|
||||
Container::Span => {
|
||||
if has_role(attrs, "datetime") {
|
||||
self.states.push(State::Datetime(String::new()))
|
||||
} else if has_role(attrs, "date") {
|
||||
self.states.push(State::Date(String::new()))
|
||||
} else {
|
||||
out.write_str("<span>")?
|
||||
}
|
||||
}
|
||||
Container::Verbatim => out.write_str("<code>")?,
|
||||
Container::Subscript => out.write_str("<sub>")?,
|
||||
Container::Superscript => out.write_str("<sup>")?,
|
||||
|
@ -365,7 +376,6 @@ impl<'s> Writer<'s> {
|
|||
Container::Strong => out.write_str("<strong>")?,
|
||||
Container::Emphasis => out.write_str("<em>")?,
|
||||
Container::Mark => out.write_str("<mark>")?,
|
||||
Container::LinkDefinition { .. } => return Ok(()),
|
||||
e => bail!("DJot element {e:?} is not supported"),
|
||||
}
|
||||
}
|
||||
|
@ -455,7 +465,6 @@ impl<'s> Writer<'s> {
|
|||
// }}}
|
||||
}
|
||||
Container::Image(src, ..) => write!(out, r#" {}>"#, Attr("src", src))?,
|
||||
Container::LinkDefinition { .. } => self.states.push(State::Ignore),
|
||||
Container::Blockquote => out.write_str("</blockquote>")?,
|
||||
Container::ListItem { .. } => out.write_str("</li>")?,
|
||||
Container::DescriptionList => out.write_str("</dl>")?,
|
||||
|
@ -548,7 +557,31 @@ impl<'s> Writer<'s> {
|
|||
out.write_str("</code></pre>")?
|
||||
}
|
||||
// }}}
|
||||
Container::Span => out.write_str("</span>")?,
|
||||
Container::Span => {
|
||||
if matches!(self.states.last(), Some(State::Datetime(_))) {
|
||||
let Some(State::Datetime(buffer)) = self.states.pop() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
write_datetime(out, &DateTime::parse_from_rfc3339(&buffer)?)?;
|
||||
} else if matches!(self.states.last(), Some(State::Date(_))) {
|
||||
let Some(State::Date(buffer)) = self.states.pop() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let date = NaiveDate::parse_from_str(&buffer, "%Y-%m-%d")
|
||||
.with_context(|| "Failed to parse date inside span")?;
|
||||
write!(
|
||||
out,
|
||||
r#"<time datetime="{}">{}</time>"#,
|
||||
date.format("%Y-%m-%d"),
|
||||
date.format("%a, %d %b %Y")
|
||||
)?;
|
||||
} else {
|
||||
out.write_str("</span>")?;
|
||||
}
|
||||
}
|
||||
|
||||
Container::Link(..) => out.write_str("</a>")?,
|
||||
Container::Verbatim => out.write_str("</code>")?,
|
||||
Container::Subscript => out.write_str("</sub>")?,
|
||||
|
@ -565,7 +598,9 @@ impl<'s> Writer<'s> {
|
|||
// {{{ Raw string
|
||||
Event::Str(s) => match self.states.last_mut() {
|
||||
Some(State::Raw) => out.write_str(s)?,
|
||||
Some(State::CodeBlock(buffer)) => buffer.push_str(s),
|
||||
Some(State::CodeBlock(buffer) | State::Datetime(buffer) | State::Date(buffer)) => {
|
||||
buffer.push_str(s)
|
||||
}
|
||||
// {{{ Math
|
||||
Some(State::Math(display)) => {
|
||||
let config = pulldown_latex::RenderConfig {
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::process::Command;
|
|||
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use chrono::{DateTime, FixedOffset, Utc};
|
||||
use jotdown::{Container, Event};
|
||||
use jotdown::{Attributes, Container, Event};
|
||||
use serde::Deserialize;
|
||||
|
||||
// {{{ Config
|
||||
|
@ -196,10 +196,8 @@ impl<'s> Writer<'s> {
|
|||
// {{{ TOML config blocks
|
||||
Event::Start(Container::RawBlock { format: "toml" }, attrs) => {
|
||||
assert_eq!(self.state, State::Toplevel);
|
||||
if let Some(role_attr) = attrs.get_value("role") {
|
||||
if format!("{}", role_attr) == "config" {
|
||||
self.state = State::Config
|
||||
}
|
||||
if has_role(attrs, "config") {
|
||||
self.state = State::Config
|
||||
}
|
||||
}
|
||||
Event::End(Container::RawBlock { format: "toml" }) => {
|
||||
|
@ -216,10 +214,8 @@ impl<'s> Writer<'s> {
|
|||
// }}}
|
||||
// {{{ Descriptions
|
||||
Event::Start(Container::Div { .. }, attrs) if self.state == State::Toplevel => {
|
||||
if let Some(role_attr) = attrs.get_value("role") {
|
||||
if format!("{}", role_attr) == "description" {
|
||||
self.state = State::Description
|
||||
}
|
||||
if has_role(attrs, "description") {
|
||||
self.state = State::Description
|
||||
}
|
||||
}
|
||||
Event::End(Container::Div { .. }) if self.state == State::Description => {
|
||||
|
@ -243,3 +239,10 @@ impl<'s> Writer<'s> {
|
|||
}
|
||||
}
|
||||
// }}}
|
||||
// {{{ Helpers
|
||||
pub fn has_role(attrs: &Attributes, value: &str) -> bool {
|
||||
attrs
|
||||
.get_value("role")
|
||||
.map_or(false, |role| format!("{role}") == value)
|
||||
}
|
||||
// }}}
|
||||
|
|
Loading…
Reference in a new issue