mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Move common components to shared lib
This commit is contained in:
261
rc_core/src/persist/user/account_json.rs
Normal file
261
rc_core/src/persist/user/account_json.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::persist::config::ConfigProvider;
|
||||
|
||||
pub struct AccountProvider {
|
||||
root: std::path::PathBuf,
|
||||
cubes: std::sync::Arc<Vec<u32>>,
|
||||
}
|
||||
|
||||
impl AccountProvider {
|
||||
pub fn load(root: impl AsRef<std::path::Path>, cubes: &crate::persist::config::ConfigImpl) -> std::io::Result<Self> {
|
||||
let root = root.as_ref().join(super::USERS_DIR);
|
||||
std::fs::create_dir_all(&root)?;
|
||||
Ok(Self {
|
||||
root,
|
||||
cubes: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::ids(cubes)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
||||
fn authenticate(&self, token: super::UserToken) -> Result<Box<dyn super::User<C> + Send + Sync>, String> {
|
||||
let new_root = self.root.join(&token.uuid);
|
||||
if !new_root.exists() {
|
||||
std::fs::create_dir(&new_root).map_err(|e| e.to_string())?;
|
||||
log::info!("New user {}", token.uuid);
|
||||
super::setup_directory(&new_root).map_err(|e| e.to_string())?;
|
||||
}
|
||||
let account_info = AccountInfo::load(&new_root).map_err(|e| e.to_string())?;
|
||||
Ok(Box::new(UserData {
|
||||
root: new_root,
|
||||
token,
|
||||
account: account_info,
|
||||
cubes: self.cubes.clone(),
|
||||
}))
|
||||
//Err("Unable to authenticate".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct UserData {
|
||||
root: std::path::PathBuf,
|
||||
token: super::UserToken,
|
||||
account: AccountInfo,
|
||||
cubes: std::sync::Arc<Vec<u32>>,
|
||||
}
|
||||
|
||||
impl UserData {
|
||||
fn load_garage_by_id(&self, id: u32) -> std::io::Result<crate::persist::GarageSlot> {
|
||||
let path = self.root.join(super::GARAGE_DIR).join(format!("{}.json", id));
|
||||
crate::persist::GarageSlot::load(&path)
|
||||
}
|
||||
|
||||
fn save_garage(&self, slot: &crate::persist::GarageSlot) -> std::io::Result<()> {
|
||||
let path = self.root.join(super::GARAGE_DIR).join(format!("{}.json", slot.slot));
|
||||
slot.save(path)
|
||||
}
|
||||
|
||||
fn all_vehicles(&self) -> std::io::Result<Vec<crate::persist::GarageSlot>> {
|
||||
let path = self.root.join(super::GARAGE_DIR);
|
||||
let mut slots = Vec::new();
|
||||
for entry in std::fs::read_dir(path)? {
|
||||
let entry = entry?;
|
||||
let filepath = entry.path();
|
||||
if filepath.is_file() {
|
||||
let slot = crate::persist::GarageSlot::load(&filepath)?;
|
||||
slots.push(slot);
|
||||
} else {
|
||||
log::warn!("Ignoring non-file {} in {} dir", filepath.display(), super::GARAGE_DIR);
|
||||
}
|
||||
}
|
||||
slots.sort_by_key(|slot| slot.slot);
|
||||
Ok(slots)
|
||||
}
|
||||
}
|
||||
|
||||
const INVALID_ROBOT_ERR: i16 = 140;
|
||||
const DATABASE_ERR: i16 = 8;
|
||||
|
||||
impl <C: Clone> super::User<C> for UserData {
|
||||
fn token(&self) -> &'_ super::UserToken {
|
||||
&self.token
|
||||
}
|
||||
|
||||
fn is_mod(&self) -> bool {
|
||||
self.account.is_mod
|
||||
}
|
||||
|
||||
fn is_admin(&self) -> bool {
|
||||
self.account.is_admin
|
||||
}
|
||||
|
||||
fn is_dev(&self) -> bool {
|
||||
self.account.is_dev
|
||||
}
|
||||
|
||||
fn unlocked_parts(&self) -> Vec<u32> {
|
||||
match self.account.inventory.override_ {
|
||||
super::inventory::UnlockOverride::Normal => self.account.inventory.unlocked.clone(),
|
||||
super::inventory::UnlockOverride::UnlockNone => Vec::default(),
|
||||
super::inventory::UnlockOverride::UnlockAll => self.cubes.as_ref().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn selected_garage_uuid(&self) -> String {
|
||||
self.account.garage.uuid_str()
|
||||
}
|
||||
|
||||
fn selected_garage_slot(&self) -> u32 {
|
||||
self.account.garage.slot
|
||||
}
|
||||
|
||||
fn all_slots_by_id(&self) -> super::UserSlots<C> {
|
||||
let slots = match self.all_vehicles() {
|
||||
Ok(slots) => slots,
|
||||
Err(e) => {
|
||||
log::error!("Failed to load all vehicles: {}", e);
|
||||
Vec::default()
|
||||
}
|
||||
};
|
||||
let slot_order = polariton::operation::Typed::ObjArr(slots.iter().map(|slot| polariton::operation::Typed::Int(slot.slot as _)).collect::<Vec<_>>().into());
|
||||
let slot_info = polariton::operation::Typed::Dict(polariton::operation::Dict {
|
||||
key_ty: polariton::serdes::TypePrefix::Int,
|
||||
val_ty: polariton::serdes:: TypePrefix::HashMap,
|
||||
items: slots.into_iter().map(|slot| {
|
||||
let slot_index = slot.slot;
|
||||
let garage_data: crate::data::garage_bay::GarageSlotInfo = slot.into();
|
||||
(polariton::operation::Typed::Int(slot_index as _), garage_data.as_transmissible())
|
||||
}).collect(),
|
||||
});
|
||||
super::UserSlots {
|
||||
slot_info, slot_order,
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_by_id(&self, id: i32) -> Result<crate::persist::user::UserSlotData<C>, i16> {
|
||||
match self.load_garage_by_id(id as _) {
|
||||
Ok(slot) => {
|
||||
let control_ty: crate::data::garage_bay::ControlType = slot.control_type.into();
|
||||
let control_options: crate::data::garage_bay::ControlOptions = slot.control_options.into();
|
||||
Ok(crate::persist::user::UserSlotData {
|
||||
data: polariton::operation::Typed::Bytes(slot.robot_data.into()),
|
||||
colour_data: polariton::operation::Typed::Bytes(slot.colour_data.into()),
|
||||
cube_count: polariton::operation::Typed::Int(slot.cubes as _),
|
||||
weapon_order: polariton::operation::Typed::IntArr(slot.weapon_order.clone().into()),
|
||||
movement_categories: polariton::operation::Typed::IntArr(slot.movement_categories.into_iter().map(|cat| {
|
||||
let cat: crate::data::weapon_list::ItemCategory = cat.into();
|
||||
cat.but_bigger()
|
||||
}).collect::<Vec<_>>().into()),
|
||||
control_type: polariton::operation::Typed::Int(control_ty as _),
|
||||
control_options: control_options.as_transmissible(),
|
||||
mastery_level: polariton::operation::Typed::Int(0), // TODO
|
||||
robot_rank: polariton::operation::Typed::Int(slot.total_robot_ranking as _),
|
||||
cpu: polariton::operation::Typed::Int(slot.total_robot_cpu as _),
|
||||
cosmetic_cpu: polariton::operation::Typed::Int(slot.total_cosmetic_cpu as _),
|
||||
uuid: polariton::operation::Typed::Str(format!("{}_{}", slot.uuid.0, slot.uuid.1).into()),
|
||||
})
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to load vehicle {}: {}", id, e);
|
||||
Err(INVALID_ROBOT_ERR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn save_slot(&self, vehicle: crate::persist::user::VehicleData) -> Result<(), i16> {
|
||||
let id = vehicle.id as u32;
|
||||
let mut existing_data = self.load_garage_by_id(id).map_err(|e| {
|
||||
log::error!("Failed to load vehicle {}: {}", id, e);
|
||||
INVALID_ROBOT_ERR
|
||||
})?;
|
||||
existing_data.slot = id;
|
||||
existing_data.robot_data = vehicle.robot_data;
|
||||
existing_data.colour_data = vehicle.colour_data;
|
||||
log::debug!("weapon order: {:?}", vehicle.weapon_order);
|
||||
existing_data.weapon_order = vehicle.weapon_order;
|
||||
self.save_garage(&existing_data).map_err(|e| {
|
||||
log::error!("Failed to save vehicle {}: {}", id, e);
|
||||
DATABASE_ERR
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn signup_date(&self) -> i64 {
|
||||
match self.root.metadata() {
|
||||
Ok(meta) => {
|
||||
match meta.created() {
|
||||
Ok(created) => {
|
||||
match created.duration_since(std::time::SystemTime::UNIX_EPOCH) {
|
||||
Ok(dur) => {
|
||||
return super::since_windows_epoch(dur.as_secs() as i64);
|
||||
},
|
||||
Err(e) => log::error!("could not get duration since unix epoch of {}: {}", self.root.display(), e),
|
||||
}
|
||||
},
|
||||
Err(e) => log::error!("could not read creation time of {}: {}", self.root.display(), e),
|
||||
}
|
||||
},
|
||||
Err(e) => log::error!("could not retrieve metadata of {}: {}", self.root.display(), e),
|
||||
}
|
||||
super::since_windows_epoch(0)
|
||||
}
|
||||
|
||||
fn singleplayer_robots(&self) -> Result<polariton::operation::Typed<C>, i16> {
|
||||
let current_slot = self.load_garage_by_id(self.account.garage.slot).map_err(|e| {
|
||||
log::error!("Failed to load current vehicle: {}", e);
|
||||
INVALID_ROBOT_ERR
|
||||
})?;
|
||||
let user_uuid = self.token.uuid.clone();
|
||||
Ok(crate::data::player_data::PlayerDatas {
|
||||
players: vec![
|
||||
crate::data::player_data::PlayerData {
|
||||
name: user_uuid.clone(),
|
||||
display_name: user_uuid,
|
||||
mastery: current_slot.mastery_level,
|
||||
tier: 1, // FIXME
|
||||
robot_name: current_slot.name,
|
||||
robot_map: current_slot.robot_data,
|
||||
team: 0,
|
||||
has_premium: true, // FIXME
|
||||
robot_uuid: format!("{}_{}", current_slot.uuid.0, current_slot.uuid.1),
|
||||
cpu: current_slot.total_robot_cpu as i32,
|
||||
weapon_order: current_slot.weapon_order.clone(),
|
||||
colour_map: current_slot.colour_data,
|
||||
is_ai: false,
|
||||
spawn_effect: "Spawn_Warp".to_owned(), // FIXME
|
||||
death_effect: "Explosion_Warp".to_owned(), // FIXME
|
||||
player_rank: 1, // FIXME
|
||||
weapon_rank: current_slot.weapon_order.into_iter().map(|x| (x, if x == 0 { 0 } else { 1 })).collect(),
|
||||
}
|
||||
],
|
||||
}.as_transmissible())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct AccountInfo {
|
||||
pub is_mod: bool,
|
||||
pub is_admin: bool,
|
||||
pub is_dev: bool,
|
||||
pub inventory: super::UnlockedParts,
|
||||
pub garage: super::SelectedGarage,
|
||||
}
|
||||
|
||||
impl AccountInfo {
|
||||
fn load(root: impl AsRef<std::path::Path>) -> std::io::Result<AccountInfo> {
|
||||
let file = std::fs::File::open(root.as_ref().join(super::USER_FILE))?;
|
||||
let buffered = std::io::BufReader::new(file);
|
||||
let result = serde_json::from_reader(buffered)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn save(&self, root: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
||||
let file = std::fs::File::create(root.as_ref().join(super::USER_FILE))?;
|
||||
let buffered = std::io::BufWriter::new(file);
|
||||
serde_json::to_writer_pretty(buffered, self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
13
rc_core/src/persist/user/garage_data.rs
Normal file
13
rc_core/src/persist/user/garage_data.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct SelectedGarage {
|
||||
pub uuid: (u32, u32),
|
||||
pub slot: u32,
|
||||
}
|
||||
|
||||
impl SelectedGarage {
|
||||
pub fn uuid_str(&self) -> String {
|
||||
format!("{}_{}", self.uuid.0, self.uuid.1)
|
||||
}
|
||||
}
|
||||
88
rc_core/src/persist/user/initial_data.rs
Normal file
88
rc_core/src/persist/user/initial_data.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
const REFERENCE_DIR: &str = "layout folder";
|
||||
|
||||
fn build_reference_directory(root: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
||||
std::fs::create_dir(&root)?;
|
||||
let garage_dir = root.as_ref().join(super::GARAGE_DIR);
|
||||
std::fs::create_dir(&garage_dir)?;
|
||||
default_user_data().save(&root)?;
|
||||
for slot in default_garage_slots() {
|
||||
let filepath = garage_dir.join(format!("{}.json", slot.slot));
|
||||
slot.save(filepath)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup_directory(new_dir: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
||||
let ref_path = new_dir.as_ref().parent().unwrap().join(REFERENCE_DIR);
|
||||
if !ref_path.exists() {
|
||||
log::debug!("Initialising reference directory {}", ref_path.display());
|
||||
build_reference_directory(&ref_path)?;
|
||||
}
|
||||
log::debug!("Copying reference directory for new user: {}", new_dir.as_ref().display());
|
||||
so::copy_dir_all(ref_path, new_dir)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_user_data() -> super::AccountInfo {
|
||||
super::AccountInfo {
|
||||
is_mod: false,
|
||||
is_admin: false,
|
||||
is_dev: false,
|
||||
inventory: super::UnlockedParts {
|
||||
unlocked: vec![],
|
||||
override_: super::inventory::UnlockOverride::Normal,
|
||||
},
|
||||
garage: super::SelectedGarage {
|
||||
uuid: (0, 0),
|
||||
slot: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn default_garage_slots() -> Vec<crate::persist::GarageSlot> {
|
||||
vec![
|
||||
crate::persist::GarageSlot {
|
||||
slot: 0,
|
||||
name: "Reverse-engineer great success! slot_name".to_owned(),
|
||||
cubes: 1,
|
||||
crf_id: 0,
|
||||
was_rated: false,
|
||||
movement_categories: vec![crate::persist::ItemCategory::Wheel],
|
||||
uuid: (0, 0),
|
||||
thumbnail_version: 0,
|
||||
total_robot_cpu: 1,
|
||||
total_cosmetic_cpu: 0,
|
||||
total_robot_ranking: 1,
|
||||
bay_cpu: 2_000,
|
||||
tutorial_robot: false,
|
||||
starter_robot_index: -1,
|
||||
control_type: crate::persist::ControlType::Camera,
|
||||
control_options: crate::persist::GarageControls { vertical_strafing: false, sideways_driving: false, tracks_turn_on_spot: false, },
|
||||
mastery_level: 1,
|
||||
bay_skin_id: "RC_MothershipSkin_Neptune_01".to_owned(), // TODO get the rest of the names
|
||||
weapon_order: vec![],
|
||||
robot_data: vec![0; 4],
|
||||
colour_data: vec![0; 4],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
mod so {
|
||||
// from https://stackoverflow.com/questions/26958489/how-to-copy-a-folder-recursively-in-rust
|
||||
use std::path::Path;
|
||||
use std::{io, fs};
|
||||
|
||||
pub(super) fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::create_dir_all(&dst)?;
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let ty = entry.file_type()?;
|
||||
if ty.is_dir() {
|
||||
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||
} else {
|
||||
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
16
rc_core/src/persist/user/inventory.rs
Normal file
16
rc_core/src/persist/user/inventory.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct UnlockedParts {
|
||||
pub unlocked: Vec<u32>,
|
||||
#[serde(rename = "override", default)]
|
||||
pub override_: UnlockOverride,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub enum UnlockOverride {
|
||||
#[default]
|
||||
Normal,
|
||||
UnlockAll,
|
||||
UnlockNone,
|
||||
}
|
||||
34
rc_core/src/persist/user/mod.rs
Normal file
34
rc_core/src/persist/user/mod.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
mod account_json;
|
||||
pub use account_json::{AccountProvider, AccountInfo};
|
||||
|
||||
mod garage_data;
|
||||
pub use garage_data::SelectedGarage;
|
||||
|
||||
mod initial_data;
|
||||
pub use initial_data::setup_directory;
|
||||
|
||||
mod inventory;
|
||||
pub use inventory::UnlockedParts;
|
||||
|
||||
mod traits;
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData};
|
||||
|
||||
pub const USERS_DIR: &str = "accounts";
|
||||
pub const USER_FILE: &str = "user.json";
|
||||
pub const GARAGE_DIR: &str = "vehicles";
|
||||
|
||||
pub type UserImpl = AccountProvider;
|
||||
|
||||
fn __must_impl<T: UserProvider<()>>() {}
|
||||
|
||||
fn __test_impl() {
|
||||
__must_impl::<UserImpl>();
|
||||
}
|
||||
|
||||
pub fn since_windows_epoch(since_unix_epoch: i64) -> i64 {
|
||||
use chrono::TimeZone;
|
||||
let windows_epoch = chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::parse_from_str("1601-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap());
|
||||
let time_in = chrono::DateTime::<chrono::Utc>::from_timestamp(since_unix_epoch, 0).unwrap();
|
||||
//let time_in = chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::from_timestamp(since_unix_epoch, 0));
|
||||
time_in.signed_duration_since(windows_epoch).num_milliseconds() * 10_000
|
||||
}
|
||||
53
rc_core/src/persist/user/traits.rs
Normal file
53
rc_core/src/persist/user/traits.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct UserToken {
|
||||
pub uuid: String,
|
||||
pub token: String,
|
||||
pub refresh_token: String,
|
||||
}
|
||||
|
||||
pub trait UserProvider<C> {
|
||||
fn authenticate(&self, user: UserToken) -> Result<Box<dyn User<C> + Send + Sync>, String>;
|
||||
}
|
||||
|
||||
pub trait User<C> {
|
||||
fn token(&self) -> &'_ super::UserToken;
|
||||
fn is_mod(&self) -> bool;
|
||||
fn is_admin(&self) -> bool;
|
||||
fn is_dev(&self) -> bool;
|
||||
fn unlocked_parts(&self) -> Vec<u32>;
|
||||
fn selected_garage_uuid(&self) -> String;
|
||||
fn selected_garage_slot(&self) -> u32;
|
||||
fn all_slots_by_id(&self) -> UserSlots<C>;
|
||||
fn slot_by_id(&self, id: i32) -> Result<UserSlotData<C>, i16>;
|
||||
fn save_slot(&self, vehicle: VehicleData) -> Result<(), i16>;
|
||||
fn signup_date(&self) -> i64;
|
||||
fn singleplayer_robots(&self) -> Result<polariton::operation::Typed<C>, i16>;
|
||||
}
|
||||
|
||||
pub struct UserSlots<C> {
|
||||
pub slot_info: polariton::operation::Typed<C>,
|
||||
pub slot_order: polariton::operation::Typed<C>,
|
||||
}
|
||||
|
||||
pub struct UserSlotData<C> {
|
||||
pub data: polariton::operation::Typed<C>,
|
||||
pub colour_data: polariton::operation::Typed<C>,
|
||||
pub cube_count: polariton::operation::Typed<C>,
|
||||
pub weapon_order: polariton::operation::Typed<C>,
|
||||
pub movement_categories: polariton::operation::Typed<C>,
|
||||
pub control_type: polariton::operation::Typed<C>,
|
||||
pub control_options: polariton::operation::Typed<C>,
|
||||
pub mastery_level: polariton::operation::Typed<C>,
|
||||
pub robot_rank: polariton::operation::Typed<C>,
|
||||
pub cpu: polariton::operation::Typed<C>,
|
||||
pub cosmetic_cpu: polariton::operation::Typed<C>,
|
||||
pub uuid: polariton::operation::Typed<C>,
|
||||
}
|
||||
|
||||
pub struct VehicleData {
|
||||
pub id: i32,
|
||||
pub robot_data: Vec<u8>,
|
||||
pub colour_data: Vec<u8>,
|
||||
pub weapon_order: Vec<i32>,
|
||||
}
|
||||
Reference in New Issue
Block a user