use std::collections::HashMap;
use bitflags::bitflags;
use plist::{Dictionary, Value};
use crate::{
ccgamemanager::{
IntMap,
achievements::{GDAchievement, MAX_ACHIEVEMENT_INDEX},
},
cclocallevels::{gdlevel::GDLevel, gdlist::GDList, gdobj::GDObject},
repr_t,
};
pub type AchievementProgress = u16;
#[derive(Debug, Default, Clone)]
pub struct GDPlayerInfo {
pub username: String,
pub udid: String,
pub user_id: i32,
pub icon_cube: i32,
pub icon_ship: i32,
pub icon_ball: i32,
pub icon_ufo: i32,
pub icon_wave: i32,
pub icon_robot: i32,
pub icon_spider: i32,
pub icon_swing: i32,
pub player_col1: i32,
pub player_col2: i32,
pub player_col_glow: i32,
pub icon_streak: i32,
pub ship_streak: i32,
pub death_effect: i32,
pub icon_jetpack: i32,
pub icon_type: i32,
pub using_glow: bool,
pub is_moderator: bool,
}
#[derive(Debug, Clone)]
pub struct GDStatistics {
pub bootups: i32,
pub official_level_progresses: Vec<GDLevel>,
pub online_levels_played: Vec<GDLevel>,
pub demon_keys: i32,
pub submitted_ratings: Vec<i32>,
pub submitted_ratings_demons: Vec<i32>,
pub gauntlet_levels_played: Vec<GDLevel>,
pub completed_dailies: IntMap<GDLevel>,
pub achievements: [AchievementProgress; MAX_ACHIEVEMENT_INDEX],
pub custom_achievements: HashMap<String, AchievementProgress>,
}
impl Default for GDStatistics {
fn default() -> Self {
Self {
bootups: 0,
official_level_progresses: Vec::new(),
online_levels_played: Vec::new(),
demon_keys: 0,
submitted_ratings: Vec::new(),
submitted_ratings_demons: Vec::new(),
gauntlet_levels_played: Vec::new(),
completed_dailies: HashMap::default(),
achievements: [0; MAX_ACHIEVEMENT_INDEX],
custom_achievements: HashMap::default(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct GDConfig {
pub bgm_volume: f32,
pub sfx_volume: f32,
pub texture_quality: TextureQuality,
pub resolution: Resolution,
pub show_song_markers: bool,
pub show_progress_bar: bool,
pub has_clicked_garage: bool,
pub has_clicked_editor: bool,
pub has_clicked_practice: bool,
pub seen_editor_guide: bool,
pub seen_ldm_dialog: bool,
pub seen_rate_star_dialog: bool,
pub has_rated_game: bool,
pub binary_version: i32,
pub practice_ui_pos: (f32, f32),
pub practice_ui_opacity: f32,
pub fps_target: f32,
pub music_offset: i32,
pub dpads: [GDPlatformerUI; 5],
pub dpad_layout: Option<GDPlatformerUI>,
pub saved_levels_foldernames: Vec<(i32, String)>,
pub local_levels_foldernames: Vec<(i32, String)>,
pub glm12_unknown: Vec<[i32; 4]>,
pub glm23_unknown: i32,
pub favourite_lists: Vec<GDList>,
pub custom_objects: Vec<(i32, Vec<GDObject>)>,
pub search_filters: GDSearchFilters,
pub secret_number: i32,
}
#[derive(Debug, Default, Clone)]
pub struct GDAccount {
pub username: String,
pub plaintext_password: Option<String>,
pub account_id: i32,
pub session_id: Option<String>,
pub hashed_password: Option<String>,
pub following_creators: Vec<i32>,
pub reported_levels: Vec<i32>,
}
#[derive(Debug, Default, Clone)]
pub struct GDCurrentValues {
pub last_played_levels: Vec<i32>,
pub current_daily_level: i32,
pub current_weekly_level: i32,
}
#[derive(Debug, Default, Clone)]
pub struct GDSongConfig {
pub stored_songs: (), pub song_priority: i32,
}
repr_t!(
strict TextureQuality: i32 {
Auto = 0,
Low = 1,
Medium = 2,
High = 3,
} default Auto
);
repr_t!(
strict Resolution: i32 {
R640x480 = 1, R720x480 = 2, R720x576 = 3, R800x600 = 4, R1024x768 = 5, R1152x864 = 6, R1176x664 = 7, R1280x720 = 8, R1280x768 = 9, R1280x800 = 10, R1280x960 = 11, R1280x1024 = 12, R1360x768 = 13, R1366x768 = 14, R1440x900 = 15, R1600x900 = 16, R1600x1024 = 17, R1600x1200 = 18, R1680x1050 = 19, R1768x992 = 20, R1920x1080 = 21, R1920x1200 = 22, R1920x1440 = 23, R2048x1536 = 24, R2560x1440 = 25, R2560x1600 = 26, R3840x2160 = 27, } default R1920x1080
);
#[derive(Debug, Default, Clone)]
pub struct GDPlatformerUI {
pub width: i32,
pub height: i32,
pub scale: f32,
pub opacity: i32,
pub pos: (f32, f32),
pub mode_b: bool,
pub deadzone: f32,
pub radius: f32,
pub snap: bool,
pub split: bool,
}
impl GDPlatformerUI {
pub fn from_str(s: &str) -> Self {
let mut this = Self::default();
let fns = &[
Self::parse_width,
Self::parse_height,
Self::parse_scale,
Self::parse_opacity,
Self::parse_pos_x,
Self::parse_pos_y,
Self::parse_mode_b,
Self::parse_deadzone,
Self::parse_radius,
Self::parse_snap,
Self::parse_split,
];
s.split(",")
.into_iter()
.enumerate()
.for_each(|(idx, s)| (fns[idx])(&mut this, s));
this
}
fn parse_width(&mut self, s: &str) {
self.width = s.parse::<i32>().unwrap();
}
fn parse_height(&mut self, s: &str) {
self.height = s.parse::<i32>().unwrap();
}
fn parse_scale(&mut self, s: &str) {
self.scale = s.parse::<f32>().unwrap();
}
fn parse_opacity(&mut self, s: &str) {
self.opacity = s.parse::<i32>().unwrap();
}
fn parse_pos_x(&mut self, s: &str) {
self.pos.0 = s.parse::<f32>().unwrap();
}
fn parse_pos_y(&mut self, s: &str) {
self.pos.1 = s.parse::<f32>().unwrap();
}
fn parse_mode_b(&mut self, s: &str) {
self.mode_b = s.parse::<i32>().unwrap() == 1;
}
fn parse_deadzone(&mut self, s: &str) {
self.deadzone = s.parse::<f32>().unwrap();
}
fn parse_radius(&mut self, s: &str) {
self.radius = s.parse::<f32>().unwrap();
}
fn parse_snap(&mut self, s: &str) {
self.snap = s.parse::<i32>().unwrap() == 1;
}
fn parse_split(&mut self, s: &str) {
self.split = s.parse::<i32>().unwrap() == 1;
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct GDSearchFilters {
pub boolean_flags: GDSearchFilterFlags,
pub song: Option<i32>,
pub other: HashMap<String, Value>,
}
bitflags! {
#[derive(Debug, Copy, Clone, PartialEq, Default, Eq, Hash)]
#[must_use]
pub struct GDSearchFilterFlags: u32 {
const DifficultyNA = 1;
const DifficultyAuto = 1 << 1;
const DifficultyEasy = 1 << 2;
const DifficultyNormal = 1 << 3;
const DifficultyHard = 1 << 4;
const DifficultyHarder = 1 << 5;
const DifficultyInsane = 1 << 6;
const DifficultyDemon = 1 << 7;
const LengthTiny = 1 << 8;
const LengthSmall = 1 << 9;
const LengthMedium = 1 << 10;
const LengthLong = 1 << 11;
const LengthXL = 1 << 12;
const DemonFilter = 1 << 13;
const Platformer = 1 << 14;
const HasStars = 1 << 15;
const CustomSongFilter = 1 << 16;
const IsMythic = 1 << 18;
const SongFilterEnabled = 1 << 19;
const IsUncompleted = 1 << 20;
const IsCompleted = 1 << 21;
const IsFeatured = 1 << 22;
const IsOriginal = 1 << 23;
const TwoPlayer = 1 << 24;
const NoStars = 1 << 25;
const HasCoins = 1 << 26;
const FollowedCreator = 1 << 27;
const Friends = 1 << 28;
const RatingEpic = 1 << 29;
const RatingLegendary = 1 << 30;
}
}
impl GDSearchFilters {
pub fn from_dict(d: &Dictionary) -> Option<Self> {
let mut this = Self::default();
for (k, v) in d.iter() {
let bool_value = v.as_string()? == "1";
match k.as_str() {
"Diff0" => this
.boolean_flags
.set(GDSearchFilterFlags::DifficultyNA, bool_value),
"Diff1" => this
.boolean_flags
.set(GDSearchFilterFlags::DifficultyAuto, bool_value),
"Diff2" => this
.boolean_flags
.set(GDSearchFilterFlags::DifficultyEasy, bool_value),
"Diff3" => this
.boolean_flags
.set(GDSearchFilterFlags::DifficultyNormal, bool_value),
"Diff4" => this
.boolean_flags
.set(GDSearchFilterFlags::DifficultyHard, bool_value),
"Diff5" => this
.boolean_flags
.set(GDSearchFilterFlags::DifficultyHarder, bool_value),
"Diff6" => this
.boolean_flags
.set(GDSearchFilterFlags::DifficultyInsane, bool_value),
"Diff7" => this
.boolean_flags
.set(GDSearchFilterFlags::DifficultyDemon, bool_value),
"Len0" => this
.boolean_flags
.set(GDSearchFilterFlags::LengthTiny, bool_value),
"Len1" => this
.boolean_flags
.set(GDSearchFilterFlags::LengthSmall, bool_value),
"Len2" => this
.boolean_flags
.set(GDSearchFilterFlags::LengthMedium, bool_value),
"Len3" => this
.boolean_flags
.set(GDSearchFilterFlags::LengthLong, bool_value),
"Len4" => this
.boolean_flags
.set(GDSearchFilterFlags::LengthXL, bool_value),
"demon_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::DemonFilter, bool_value),
"Len5" => this
.boolean_flags
.set(GDSearchFilterFlags::Platformer, bool_value),
"star_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::HasStars, bool_value),
"customsong_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::CustomSongFilter, bool_value),
"mythic_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::IsMythic, bool_value),
"enable_songFilter" => this
.boolean_flags
.set(GDSearchFilterFlags::SongFilterEnabled, bool_value),
"uncompleted_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::IsUncompleted, bool_value),
"completed_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::IsCompleted, bool_value),
"featured_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::IsFeatured, bool_value),
"original_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::IsOriginal, bool_value),
"twoP_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::TwoPlayer, bool_value),
"nostar_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::NoStars, bool_value),
"coin_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::HasCoins, bool_value),
"follow_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::FollowedCreator, bool_value),
"friend_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::Friends, bool_value),
"epic_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::RatingEpic, bool_value),
"legendary_filter" => this
.boolean_flags
.set(GDSearchFilterFlags::RatingLegendary, bool_value),
"song_filter" => this.song = Some(v.as_string()?.parse::<i32>().ok()?),
_ => {
this.other.insert(k.clone(), v.clone());
}
}
}
Some(this)
}
}
impl GDStatistics {
#[inline]
pub fn get_vanilla_achievement(&self, a: GDAchievement) -> AchievementProgress {
self.achievements[a as i32 as usize - 1]
}
#[inline]
pub fn get_achievement_by_ident(&self, ident: &str) -> Option<AchievementProgress> {
match GDAchievement::parse_str(ident) {
Some(a) => Some(self.get_vanilla_achievement(a)),
None => {
self.custom_achievements.get(ident).copied()
}
}
}
#[inline]
pub fn set_vanilla_achievement(&mut self, a: GDAchievement, value: AchievementProgress) {
self.achievements[a as i32 as usize - 1] = value;
}
#[inline]
pub fn set_achievement_by_ident(&mut self, ident: &str, value: AchievementProgress) {
match GDAchievement::parse_str(ident) {
Some(a) => self.set_vanilla_achievement(a, value),
None => {
let _ = self.custom_achievements.insert(ident.to_string(), value);
}
}
}
}