feat: implement rate-up

This commit is contained in:
Rekai Nyangadzayi Musuka 2021-02-09 14:42:09 -06:00
parent 4962ee3d79
commit aa68942d84
7 changed files with 627 additions and 218 deletions

View File

@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "^0.8"
rand = { version = "^0.8", features = ["alloc"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
serde_repr = "^0.1"

View File

@ -1,24 +1,29 @@
use std::convert::TryInto;
use rand::{prelude::SliceRandom, Rng};
use crate::{
gacha::Rarity,
student::{self, Student},
};
use crate::{
gacha::{Gacha, Recruitment},
i18n::{I18nString, Language},
};
use crate::gacha::{Gacha, Rarity, Recruitment};
use crate::i18n::{I18nString, Language};
use crate::student::Student;
use rand::distributions::{Distribution, WeightedIndex};
use rand::Rng;
use std::convert::{TryFrom, TryInto};
/// Used to Construct a Banner
#[derive(Debug, Default)]
pub struct BannerBuilder {
name: I18nString,
rates: Option<Gacha>,
gacha: Option<Gacha>,
sparkable: Option<Vec<Student>>,
}
impl BannerBuilder {
/// Creates a new instance of a BannerBuilder
///
/// # Arguments
/// * `jpn_name` - The name of the Banner as seen in Blue Archive
///
/// # Examples
/// ```
/// # use ba_gacha::banner::BannerBuilder;
/// let banner_builder = BannerBuilder::new("ピックアップ募集");
/// ```
pub fn new(jpn_name: &str) -> Self {
Self {
name: I18nString::new(jpn_name),
@ -26,21 +31,58 @@ impl BannerBuilder {
}
}
/// Adds a Translation to the internal I18nString
///
/// # Arguments
/// * `language` - The target language.
/// * `name` - The name of the Banner in the target language.
///
/// # Examples
/// ```
/// # use ba_gacha::banner::BannerBuilder;
/// # use ba_gacha::i18n::Language;
/// let banner_builder = BannerBuilder::new("ピックアップ募集")
/// .with_name_translation(Language::English, "Focus Recruitment");
/// ```
pub fn with_name_translation(mut self, language: Language, name: &str) -> Self {
self.name.update(language, name);
self
}
pub fn with_rates(self, rates: &Gacha) -> Self {
/// Adds an instance of a Gacha struct
///
/// # Arguments
/// * `gacha` - An instance of a Gacha struct
///
/// # Examples
/// ```
/// # use ba_gacha::gacha::GachaBuilder;
/// # use ba_gacha::banner::BannerBuilder;
/// let gacha = GachaBuilder::default()
/// .with_pool(Vec::new())
/// .finish().unwrap();
///
/// let banner_builder = BannerBuilder::new("ピックアップ募集")
/// .with_gacha(&gacha);
/// ```
pub fn with_gacha(self, gacha: &Gacha) -> Self {
Self {
rates: Some(rates.to_owned()),
gacha: Some(gacha.to_owned()),
..self
}
}
pub fn with_sparkable_students(self, students: Vec<Student>) -> Self {
/// Adds a vector containing all sparkable students in the current Banner
///
/// # Arguments
/// * `students` - A Vector of Students which are sparkable
///
/// # Examples
/// ```
/// # use ba_gacha::banner::BannerBuilder;
pub fn with_sparkable_students(self, students: &[Student]) -> Self {
Self {
sparkable: Some(students),
sparkable: Some(students.to_vec()),
..self
}
}
@ -48,56 +90,99 @@ impl BannerBuilder {
pub fn finish(self) -> Option<Banner> {
Some(Banner {
name: self.name,
rates: self.rates?,
sparkable: self.sparkable?,
gacha: self.gacha?,
sparkable: self.sparkable,
})
}
}
#[derive(Debug, Clone, Copy)]
enum StudentType {
One = 1, // One Star
Two, // Two Stars
Three, // Three Stars
Priority, // Rate-up Student (Presumably 3*)
}
impl From<Rarity> for StudentType {
fn from(rarity: Rarity) -> Self {
match rarity {
Rarity::One => Self::One,
Rarity::Two => Self::Two,
Rarity::Three => Self::Three,
}
}
}
impl TryFrom<StudentType> for Rarity {
type Error = &'static str;
fn try_from(value: StudentType) -> Result<Self, Self::Error> {
Ok(match value {
StudentType::One => Self::One,
StudentType::Two => Self::Two,
StudentType::Three => Self::Three,
StudentType::Priority => return Err("Can not convert from Priority to Rarity"),
})
}
}
pub struct Banner {
pub name: I18nString,
rates: Gacha,
sparkable: Vec<Student>, // FIXME: Can we safely assume this will only contain 2 studnets?
gacha: Gacha,
sparkable: Option<Vec<Student>>,
}
impl Banner {
fn get_random_student(&self, rarity: Rarity) -> Student {
fn get_random_student(&self) -> Student {
let mut rng = rand::thread_rng();
let priority_rate = self.gacha.priority.as_ref().map_or(0, |tuple| tuple.1);
let three_star_rate = self.gacha.get_rate(Rarity::Three) - priority_rate;
let mut filtered_students: Vec<&Student> = self
.rates
.pool
let items: [(StudentType, usize); 4] = [
(StudentType::One, self.gacha.get_rate(Rarity::One)),
(StudentType::Two, self.gacha.get_rate(Rarity::Two)),
(StudentType::Three, three_star_rate),
(StudentType::Priority, priority_rate),
];
let dist = WeightedIndex::new(items.iter().map(|item| item.1)).unwrap();
let students = &self.gacha.pool;
match items[dist.sample(&mut rng)] {
(StudentType::Priority, _) => {
let priority_students = &self.gacha.priority.as_ref().unwrap().0;
let index: usize = rng.gen_range(0..priority_students.len());
priority_students[index].clone()
}
(rarity, _) => {
let students: Vec<&Student> = students
.iter()
.filter(|student| student.rarity == rarity.try_into().unwrap())
.collect();
let index: usize = rng.gen_range(0..students.len());
students[index].clone()
}
}
}
fn get_random_student_of_rarity(&self, rarity: Rarity) -> Student {
let students = &self.gacha.pool;
let two_star_students: Vec<&Student> = students
.iter()
.filter(|student| student.rarity == rarity)
.collect();
filtered_students.shuffle(&mut rng);
let index: usize = rng.gen_range(0..filtered_students.len());
filtered_students[index].clone()
}
fn get_random_rarity(&self) -> Rarity {
let mut rng = rand::thread_rng();
let random_num: usize = rng.gen_range(0..1000);
let one_rate = self.rates.get_rate(Rarity::One);
let two_rate = self.rates.get_rate(Rarity::Two);
let three_rate = self.rates.get_rate(Rarity::Three);
if random_num < three_rate {
Rarity::Three
} else if random_num < three_rate + two_rate {
Rarity::Two
} else {
Rarity::One
}
let index = rand::thread_rng().gen_range(0..two_star_students.len());
two_star_students[index].clone()
}
}
impl Recruitment for Banner {
fn roll(&self) -> Student {
self.get_random_student(self.get_random_rarity())
self.get_random_student()
}
fn roll10(&self) -> [Student; 10] {
@ -105,14 +190,14 @@ impl Recruitment for Banner {
// Fill students with 10 random students
for student in students.iter_mut() {
*student = self.get_random_student(self.get_random_rarity());
*student = self.get_random_student()
}
let two_star_present = students.iter().any(|student| student.rarity == Rarity::Two);
if !two_star_present {
if students[students.len() - 1].rarity != Rarity::Three {
students[students.len() - 1] = self.get_random_student(Rarity::Two);
students[students.len() - 1] = self.get_random_student_of_rarity(Rarity::Two);
}
}

View File

@ -43,6 +43,7 @@ pub trait Recruitment {
pub struct GachaBuilder {
rates: Option<(usize, usize, usize)>,
pool: Option<Vec<Student>>,
priority: Option<(Vec<Student>, usize)>,
}
impl Default for GachaBuilder {
@ -50,6 +51,7 @@ impl Default for GachaBuilder {
Self {
rates: Some((790, 185, 25)),
pool: Default::default(),
priority: Default::default(),
}
}
}
@ -78,7 +80,7 @@ impl GachaBuilder {
Self {
rates: Some((one, two, three)),
pool: Default::default(),
..Default::default()
}
}
@ -103,6 +105,29 @@ impl GachaBuilder {
}
}
/// Attaches a pool of Students who have increased rates
///
/// # Arugments
/// * `students` - A Vector of Students who have increased rates
/// * `rate` - The rate of the students in the previous argument
///
/// # Examples
/// ```
/// # use ba_gacha::gacha::{GachaBuilder, Rarity};
/// # use ba_gacha::student::Student;
/// let aru = Student::new("アル", Rarity::Three);
/// let hina = Student::new("ヒナ", Rarity::Three);
/// let gacha_builder = GachaBuilder::new(79.0, 18.5, 2.5)
/// .with_pool(vec![aru, hina.clone()])
/// .with_priority(vec![hina], 3.5);
/// ```
pub fn with_priority(self, students: &[Student], total_rate: f32) -> Self {
Self {
priority: Some((students.to_vec(), (total_rate * 10.0) as usize)),
..self
}
}
/// Consumes a GachaBuilder and retuns a Gacha Struct.
///
/// Will return `None` if the `rates` or `pool` property of
@ -122,6 +147,7 @@ impl GachaBuilder {
Some(Gacha {
rates: self.rates?,
pool: self.pool?,
priority: self.priority,
})
}
}
@ -130,8 +156,10 @@ impl GachaBuilder {
/// to randomly select a Student from the gacha pool
#[derive(Debug, Default, Clone)]
pub struct Gacha {
pub rates: (usize, usize, usize), // One Star, Two Star, Three Star
/// (1★, 2★, 3★)
pub rates: (usize, usize, usize),
pub pool: Vec<Student>,
pub priority: Option<(Vec<Student>, usize)>,
}
impl Gacha {

View File

@ -7,6 +7,24 @@ pub struct I18nString {
translations: HashMap<Language, String>,
}
impl PartialEq for I18nString {
fn eq(&self, other: &Self) -> bool {
self.get(Language::Japanese).unwrap() == other.get(Language::Japanese).unwrap()
}
}
impl PartialEq<String> for I18nString {
fn eq(&self, other: &String) -> bool {
self.get(Language::Japanese).unwrap() == *other
}
}
impl PartialEq<&str> for I18nString {
fn eq(&self, other: &&str) -> bool {
self.get(Language::Japanese).unwrap() == *other
}
}
impl std::fmt::Display for I18nString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.translations.get(&Language::Japanese).unwrap().as_str())

View File

@ -1,174 +1,46 @@
use std::io::Write;
use ba_gacha::{
banner::BannerBuilder,
gacha::{GachaBuilder, Rarity, Recruitment},
i18n::Language,
student::Student,
};
use ba_gacha::banner::BannerBuilder;
use ba_gacha::gacha::{GachaBuilder, Recruitment};
use ba_gacha::i18n::Language;
use ba_gacha::student::Student;
use std::{fs::File, io::Read};
fn main() {
// Create a Struct representing Shiroko
let mut shiroko = Student::new("シロコ", Rarity::Three);
shiroko.add_translation(Language::English, "Shiroko"); // Yes, this is a translation.
let mut file = File::open("students.json").unwrap();
let mut json = String::new();
file.read_to_string(&mut json).unwrap();
// Create a Struct representing Hoshino
let mut hoshino = Student::new("ホシノ", Rarity::Three);
hoshino.add_translation(Language::English, "Hoshino");
let students: Vec<Student> = serde_json::from_str(&json).unwrap();
// Create a Vector Containing all the Sparkable Students
let sparkable_students = vec![shiroko, hoshino];
let banner_students: Vec<Student> = students
.iter()
.filter(|student| student.name != "ノゾミ")
.map(|student| student.clone())
.collect();
let rates = GachaBuilder::new(79.0, 18.5, 2.5)
.with_pool(pickup_banner_2021_02_11())
let hoshino = find_student(&students, "ホシノ").unwrap();
let shiroko = find_student(&students, "シロコ").unwrap();
let rate_up_students = vec![shiroko, hoshino];
let gacha = GachaBuilder::new(79.0, 18.5, 2.5)
.with_pool(banner_students)
.with_priority(&rate_up_students, 0.7)
.finish()
.unwrap();
// Create the Banner Itself
let pickup_banner = BannerBuilder::new("ピックアップ募集")
.with_name_translation(Language::English, "Priority")
.with_sparkable_students(sparkable_students)
.with_rates(&rates)
.with_name_translation(Language::English, "Rate-Up Registration")
.with_sparkable_students(&rate_up_students)
.with_gacha(&gacha)
.finish()
.unwrap();
// Roll Banner 1 Time
let student = pickup_banner.roll();
// Roll Banner 10 Times
let students = pickup_banner.roll10();
// Display Results
println!("{} Single: {}", pickup_banner.name, student);
println!();
println!("{} 10-roll:", pickup_banner.name);
for student in students.iter() {
println!("{}", student);
}
// let mut file = std::fs::File::create("students.json").unwrap();
// let students = all_students();
// let json = serde_json::to_string(&students).unwrap();
// file.write_all(json.as_bytes()).unwrap();
// let student = pickup_banner.roll();
// let students = pickup_banner.roll10();
}
fn create_student(jpn_name: &str, eng_name: &str, rarity: Rarity) -> Student {
let mut student = Student::new(jpn_name, rarity);
student.add_translation(Language::English, eng_name);
student
}
fn pickup_banner_2021_02_11() -> Vec<Student> {
let mut test: Vec<Student> = vec![];
// 3 Star
test.push(create_student("ヒナ", "Hina", Rarity::Three));
test.push(create_student("イオリ", "Iori", Rarity::Three));
test.push(create_student("ハルナ", "Haruna", Rarity::Three));
test.push(create_student("イズミ", "Izumi", Rarity::Three));
test.push(create_student("アル", "Aru", Rarity::Three));
test.push(create_student("スミレ", "Sumire", Rarity::Three));
test.push(create_student("エイミ", "Iemi", Rarity::Three));
test.push(create_student("カリン", "Karin", Rarity::Three));
test.push(create_student("ネル", "Neru", Rarity::Three));
test.push(create_student("マキ", "Maki", Rarity::Three));
test.push(create_student("ヒビキ", "Hibiki", Rarity::Three));
test.push(create_student("サヤ", "Saya", Rarity::Three));
test.push(create_student("シュン", "Shun", Rarity::Three));
test.push(create_student("シロコ", "Shiroko", Rarity::Three));
test.push(create_student("ホシノ", "Hoshino", Rarity::Three));
test.push(create_student("ヒフミ", "Hifumi", Rarity::Three));
test.push(create_student("ツルギ", "Tsurugi", Rarity::Three));
// Two Star
test.push(create_student("アカリ", "Akari", Rarity::Two));
test.push(create_student("ジュンコ", "Junko", Rarity::Two));
test.push(create_student("ムツキ", "Mutsuki", Rarity::Two));
test.push(create_student("カヨコ", "Kayoko", Rarity::Two));
test.push(create_student("フウカ", "Fuuka", Rarity::Two));
test.push(create_student("ユウカ", "Yuuka", Rarity::Two));
test.push(create_student("アカネ", "Akane", Rarity::Two));
test.push(create_student("ハル", "Haru", Rarity::Two));
test.push(create_student("ウタハ", "Utaha", Rarity::Two));
test.push(create_student("チセ", "Chise", Rarity::Two));
test.push(create_student("ツバキ", "Tsubaki", Rarity::Two));
test.push(create_student("セリカ", "Serika", Rarity::Two));
test.push(create_student("アヤネ", "Ayane", Rarity::Two));
test.push(create_student("ハスミ", "Hasumi", Rarity::Two));
test.push(create_student("ハナエ", "Hanae", Rarity::Two));
test.push(create_student("アイリ", "Airi", Rarity::Two));
// 1 Star:
test.push(create_student("チナツ", "Chinatsu", Rarity::One));
test.push(create_student("ハルカ", "Haruka", Rarity::One));
test.push(create_student("ジュリ", "Juri", Rarity::One));
test.push(create_student("コタマ", "Kotama", Rarity::One));
test.push(create_student("アスナ", "Asuna", Rarity::One));
test.push(create_student("コトリ", "Kotori", Rarity::One));
test.push(create_student("フィーナ", "Pina", Rarity::One));
test.push(create_student("スズミ", "Suzumi", Rarity::One));
test.push(create_student("シミコ", "Shimiko", Rarity::One));
test.push(create_student("セリナ", "Serina", Rarity::One));
test.push(create_student("ヨシミ", "Yoshimi", Rarity::One));
test
}
fn all_students() -> Vec<Student> {
let mut students: Vec<Student> = Vec::with_capacity(45);
// 3 Star
students.push(create_student("ヒナ", "Hina", Rarity::Three));
students.push(create_student("イオリ", "Iori", Rarity::Three));
students.push(create_student("ハルナ", "Haruna", Rarity::Three));
students.push(create_student("イズミ", "Izumi", Rarity::Three));
students.push(create_student("アル", "Aru", Rarity::Three));
students.push(create_student("スミレ", "Sumire", Rarity::Three));
students.push(create_student("エイミ", "Iemi", Rarity::Three));
students.push(create_student("カリン", "Karin", Rarity::Three));
students.push(create_student("ネル", "Neru", Rarity::Three));
students.push(create_student("マキ", "Maki", Rarity::Three));
students.push(create_student("ヒビキ", "Hibiki", Rarity::Three));
students.push(create_student("サヤ", "Saya", Rarity::Three));
students.push(create_student("シュン", "Shun", Rarity::Three));
students.push(create_student("シロコ", "Shiroko", Rarity::Three));
students.push(create_student("ホシノ", "Hoshino", Rarity::Three));
students.push(create_student("ヒフミ", "Hifumi", Rarity::Three));
students.push(create_student("ツルギ", "Tsurugi", Rarity::Three));
// Two Star
students.push(create_student("アカリ", "Akari", Rarity::Two));
students.push(create_student("ジュンコ", "Junko", Rarity::Two));
students.push(create_student("ムツキ", "Mutsuki", Rarity::Two));
students.push(create_student("カヨコ", "Kayoko", Rarity::Two));
students.push(create_student("フウカ", "Fuuka", Rarity::Two));
students.push(create_student("ユウカ", "Yuuka", Rarity::Two));
students.push(create_student("アカネ", "Akane", Rarity::Two));
students.push(create_student("ハル", "Haru", Rarity::Two));
students.push(create_student("ウタハ", "Utaha", Rarity::Two));
students.push(create_student("チセ", "Chise", Rarity::Two));
students.push(create_student("ツバキ", "Tsubaki", Rarity::Two));
students.push(create_student("セリカ", "Serika", Rarity::Two));
students.push(create_student("アヤネ", "Ayane", Rarity::Two));
students.push(create_student("ハスミ", "Hasumi", Rarity::Two));
students.push(create_student("ハナエ", "Hanae", Rarity::Two));
students.push(create_student("アイリ", "Airi", Rarity::Two));
students.push(create_student("ノゾミ", "Nozomi", Rarity::Two)); // Not a part of Gacha
// 1 Star:
students.push(create_student("チナツ", "Chinatsu", Rarity::One));
students.push(create_student("ハルカ", "Haruka", Rarity::One));
students.push(create_student("ジュリ", "Juri", Rarity::One));
students.push(create_student("コタマ", "Kotama", Rarity::One));
students.push(create_student("アスナ", "Asuna", Rarity::One));
students.push(create_student("コトリ", "Kotori", Rarity::One));
students.push(create_student("フィーナ", "Pina", Rarity::One));
students.push(create_student("スズミ", "Suzumi", Rarity::One));
students.push(create_student("シミコ", "Shimiko", Rarity::One));
students.push(create_student("セリナ", "Serina", Rarity::One));
students.push(create_student("ヨシミ", "Yoshimi", Rarity::One));
pub fn find_student(students: &[Student], jpn_name: &str) -> Option<Student> {
students
.iter()
.find(|student| student.name == jpn_name)
.map(|student| student.clone())
}

View File

@ -1,6 +1,5 @@
use crate::gacha::Rarity;
use crate::i18n::I18nString;
use crate::i18n::Language;
use crate::i18n::{I18nString, Language};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]

407
students.json Normal file
View File

@ -0,0 +1,407 @@
[
{
"name": {
"translations": {
"jpn": "ヒナ",
"eng": "Hina"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"jpn": "イオリ",
"eng": "Iori"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"jpn": "ハルナ",
"eng": "Haruna"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"jpn": "イズミ",
"eng": "Izumi"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Aru",
"jpn": "アル"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"jpn": "スミレ",
"eng": "Sumire"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"jpn": "エイミ",
"eng": "Iemi"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Karin",
"jpn": "カリン"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"jpn": "ネル",
"eng": "Neru"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Maki",
"jpn": "マキ"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Hibiki",
"jpn": "ヒビキ"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Saya",
"jpn": "サヤ"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Shun",
"jpn": "シュン"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Shiroko",
"jpn": "シロコ"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Hoshino",
"jpn": "ホシノ"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"jpn": "ヒフミ",
"eng": "Hifumi"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"eng": "Tsurugi",
"jpn": "ツルギ"
}
},
"rarity": 3
},
{
"name": {
"translations": {
"jpn": "アカリ",
"eng": "Akari"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "ジュンコ",
"eng": "Junko"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "ムツキ",
"eng": "Mutsuki"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"eng": "Kayoko",
"jpn": "カヨコ"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "フウカ",
"eng": "Fuuka"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "ユウカ",
"eng": "Yuuka"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"eng": "Akane",
"jpn": "アカネ"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "ハル",
"eng": "Haru"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"eng": "Utaha",
"jpn": "ウタハ"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "チセ",
"eng": "Chise"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"eng": "Tsubaki",
"jpn": "ツバキ"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "セリカ",
"eng": "Serika"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "アヤネ",
"eng": "Ayane"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"eng": "Hasumi",
"jpn": "ハスミ"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"eng": "Hanae",
"jpn": "ハナエ"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"eng": "Airi",
"jpn": "アイリ"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"jpn": "ノゾミ",
"eng": "Nozomi"
}
},
"rarity": 2
},
{
"name": {
"translations": {
"eng": "Chinatsu",
"jpn": "チナツ"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"eng": "Haruka",
"jpn": "ハルカ"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"jpn": "ジュリ",
"eng": "Juri"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"jpn": "コタマ",
"eng": "Kotama"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"jpn": "アスナ",
"eng": "Asuna"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"jpn": "コトリ",
"eng": "Kotori"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"jpn": "フィーナ",
"eng": "Pina"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"jpn": "スズミ",
"eng": "Suzumi"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"eng": "Shimiko",
"jpn": "シミコ"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"eng": "Serina",
"jpn": "セリナ"
}
},
"rarity": 1
},
{
"name": {
"translations": {
"eng": "Yoshimi",
"jpn": "ヨシミ"
}
},
"rarity": 1
}
]