mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
feat-webgarages-121 (#129)
### Description Closes #121 ### Please confirm - [x] I am the legal owner or represent the legal owner of all work submitted (including LLM-generated code, if any) - [x] I consent to my changes being added to this FOSS project - [x] I have confirmed that this does not add new errors or warnings with `utils/clippy.sh` - [ ] This PR used LLMs to generate some or all of the code changes Reviewed-on: https://git.ngram.ca/OpenJam/rc-servers/pulls/129
This commit is contained in:
334
rc_core/src/cubes/conversion.rs
Normal file
334
rc_core/src/cubes/conversion.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
const COLOUR_COUNT: usize = 32;
|
||||
|
||||
struct ClassicModernTranslation {
|
||||
id_mapping: ModernMapping,
|
||||
offset: (i16, i16, i16),
|
||||
orientation_mapping: OrientationMapping,
|
||||
}
|
||||
|
||||
impl ClassicModernTranslation {
|
||||
fn convert(&self, cube: &super::Cube) -> (super::Cube, super::Colour){
|
||||
let (new_id, new_colour) = self.id_mapping.convert(cube);
|
||||
let new_orientation = self.orientation_mapping.convert(cube);
|
||||
let new_cube = super::Cube {
|
||||
id: new_id,
|
||||
x: (cube.x as u16).saturating_add_signed(self.offset.0) as _,
|
||||
y: (cube.y as u16).saturating_add_signed(self.offset.1) as _,
|
||||
z: (cube.z as u16).saturating_add_signed(self.offset.2) as _,
|
||||
orientation: new_orientation,
|
||||
};
|
||||
let new_colour = super::Colour {
|
||||
colour: new_colour,
|
||||
x: new_cube.x,
|
||||
y: new_cube.y,
|
||||
z: new_cube.z,
|
||||
};
|
||||
(new_cube, new_colour)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum ModernMapping {
|
||||
Always(u32, u8),
|
||||
ByOrientation(Vec<(u32, u8)>),
|
||||
}
|
||||
|
||||
impl ModernMapping {
|
||||
fn convert(&self, cube: &super::Cube) -> (u32, u8) {
|
||||
match self {
|
||||
Self::Always(id, colour) => (*id, *colour),
|
||||
Self::ByOrientation(v) => v[cube.orientation as usize],
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty(&self) -> String {
|
||||
match self {
|
||||
Self::Always(id, colour) => format!("ID:{}|colour#{}", id, colour),
|
||||
Self::ByOrientation(mapping) => format!("o[0]ID:{}|colour#{}", mapping[0].0, mapping[0].1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ModernClassicTranslation {
|
||||
id_mapping: ClassicMapping,
|
||||
offset: (i16, i16, i16),
|
||||
orientation_mapping: OrientationMapping,
|
||||
}
|
||||
|
||||
impl ModernClassicTranslation {
|
||||
fn convert(&self, cube: &super::Cube, colour: &super::Colour) -> super::Cube {
|
||||
let new_id = self.id_mapping.convert(cube, colour);
|
||||
let new_orientation = self.orientation_mapping.convert(cube);
|
||||
super::Cube {
|
||||
id: new_id,
|
||||
x: (cube.x as u16).saturating_add_signed(self.offset.0) as _,
|
||||
y: (cube.y as u16).saturating_add_signed(self.offset.1) as _,
|
||||
z: (cube.z as u16).saturating_add_signed(self.offset.2) as _,
|
||||
orientation: new_orientation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum ClassicMapping {
|
||||
Always(u32),
|
||||
ByColour(Vec<u32>),
|
||||
ByOrientation(Vec<u32>),
|
||||
}
|
||||
|
||||
impl ClassicMapping {
|
||||
fn convert(&self, cube: &super::Cube, colour: &super::Colour) -> u32 {
|
||||
match self {
|
||||
Self::Always(x) => *x,
|
||||
Self::ByColour(v) => v[colour.colour as usize],
|
||||
Self::ByOrientation(v) => v[cube.orientation as usize],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum OrientationMapping {
|
||||
Passthrough,
|
||||
ByOrientation(Vec<u8>),
|
||||
}
|
||||
|
||||
impl OrientationMapping {
|
||||
fn convert(&self, cube: &super::Cube) -> u8 {
|
||||
match self {
|
||||
Self::Passthrough => cube.orientation,
|
||||
Self::ByOrientation(v) => v[cube.orientation as usize],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConversionError {
|
||||
UnknownCube {
|
||||
id: u32,
|
||||
index: usize,
|
||||
position: (u8, u8, u8)
|
||||
},
|
||||
CubeParse(std::io::Error),
|
||||
CubeDump(std::io::Error),
|
||||
ColourParse(std::io::Error),
|
||||
ColourDump(std::io::Error),
|
||||
}
|
||||
|
||||
impl core::fmt::Display for ConversionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UnknownCube { id, index, position } => write!(f, "Unknown cube ID {} at {} {:?}", id, index, position),
|
||||
Self::CubeParse(e) => write!(f, "Failed to parse cube data: {}", e),
|
||||
Self::CubeDump(e) => write!(f, "Failed to dump cube data: {}", e),
|
||||
Self::ColourParse(e) => write!(f, "Failed to parse colour data: {}", e),
|
||||
Self::ColourDump(e) => write!(f, "Failed to dump colour data: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for ConversionError {}
|
||||
|
||||
pub struct ModernConversionResult{
|
||||
pub cube_data: Vec<u8>,
|
||||
pub colour_data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct CubeConversionParser {
|
||||
classic_to_modern: std::collections::HashMap<u32, ClassicModernTranslation>,
|
||||
modern_to_classic: std::collections::HashMap<u32, ModernClassicTranslation>,
|
||||
known: std::collections::HashSet<u32>,
|
||||
}
|
||||
|
||||
impl CubeConversionParser {
|
||||
pub fn with_cubes<'a, I: std::iter::Iterator<Item=&'a crate::persist::Cube>>(cubes: I) -> Self {
|
||||
let mut classic_to_modern = std::collections::HashMap::<u32, ClassicModernTranslation>::new();
|
||||
let mut modern_to_classic = std::collections::HashMap::<u32, ModernClassicTranslation>::new();
|
||||
let mut known = std::collections::HashSet::new();
|
||||
for cube in cubes {
|
||||
// populate classic_to_modern
|
||||
let conversion_data = cube.conversion.clone().unwrap_or_else(crate::persist::conversion::actual_default_conversion);
|
||||
for from in conversion_data.from.iter() {
|
||||
let mapping = match from {
|
||||
crate::persist::FromConversionData::Simple(_id) => ClassicModernTranslation {
|
||||
id_mapping: ModernMapping::Always(cube.id, 0),
|
||||
offset: (0, 0, 0),
|
||||
orientation_mapping: OrientationMapping::Passthrough,
|
||||
},
|
||||
crate::persist::FromConversionData::Complex { id: _, offset, colour } => ClassicModernTranslation {
|
||||
id_mapping: ModernMapping::Always(cube.id, *colour),
|
||||
offset: offset.unwrap_or_default(),
|
||||
orientation_mapping: OrientationMapping::Passthrough,
|
||||
},
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(old_mapping) = classic_to_modern.get(&from.id()) {
|
||||
log::debug!("Not overriding mapping classic ID {} from modern {} to modern {}", from.id(), old_mapping.id_mapping.pretty(), mapping.id_mapping.pretty());
|
||||
} else {
|
||||
classic_to_modern.insert(from.id(), mapping);
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
if !classic_to_modern.contains_key(&from.id) {
|
||||
classic_to_modern.insert(from.id(), mapping);
|
||||
}
|
||||
}
|
||||
// populate modern_to_classic
|
||||
let mapping = if let Some(to) = conversion_data.to {
|
||||
match to {
|
||||
crate::persist::ToConversionData::Simple(id) => ModernClassicTranslation {
|
||||
id_mapping: ClassicMapping::Always(id),
|
||||
offset: (0, 0, 0),
|
||||
orientation_mapping: OrientationMapping::Passthrough,
|
||||
},
|
||||
crate::persist::ToConversionData::Complex { id, offset } => ModernClassicTranslation {
|
||||
id_mapping: ClassicMapping::Always(id),
|
||||
offset: offset.unwrap_or_default(),
|
||||
orientation_mapping: OrientationMapping::Passthrough,
|
||||
}
|
||||
}
|
||||
} else if !conversion_data.from.is_empty() {
|
||||
if conversion_data.from.len() == 1 {
|
||||
let from_first = conversion_data.from.first().unwrap();
|
||||
match from_first {
|
||||
crate::persist::FromConversionData::Simple(id) => ModernClassicTranslation {
|
||||
id_mapping: ClassicMapping::Always(*id),
|
||||
offset: (0, 0, 0),
|
||||
orientation_mapping: OrientationMapping::Passthrough,
|
||||
},
|
||||
crate::persist::FromConversionData::Complex { id, offset, colour: _ } => ModernClassicTranslation {
|
||||
id_mapping: ClassicMapping::Always(*id),
|
||||
offset: offset.map(|(x, y, z)| (-x, -y, -z)).unwrap_or_default(),
|
||||
orientation_mapping: OrientationMapping::Passthrough,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// more than one conversion, build map
|
||||
let default_id = conversion_data.from.first().unwrap().id();
|
||||
let mut colour_map: Vec<u32> = (0..COLOUR_COUNT).map(|_| default_id).collect();
|
||||
let mut is_default_set = false;
|
||||
for from in conversion_data.from.iter() {
|
||||
let colour = match from {
|
||||
crate::persist::FromConversionData::Simple(_) => 0,
|
||||
crate::persist::FromConversionData::Complex { colour, .. } => *colour,
|
||||
};
|
||||
if colour_map[colour as usize] != default_id { continue; }
|
||||
if colour == 0 {
|
||||
if !is_default_set {
|
||||
is_default_set = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
colour_map[colour as usize] = from.id();
|
||||
}
|
||||
ModernClassicTranslation {
|
||||
id_mapping: ClassicMapping::ByColour(colour_map),
|
||||
offset: (0, 0, 0),
|
||||
orientation_mapping: OrientationMapping::Passthrough,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ModernClassicTranslation {
|
||||
id_mapping: ClassicMapping::Always(cube.id),
|
||||
offset: (0, 0, 0),
|
||||
orientation_mapping: OrientationMapping::Passthrough,
|
||||
}
|
||||
};
|
||||
modern_to_classic.insert(cube.id, mapping);
|
||||
// populate known cubes
|
||||
known.insert(cube.id);
|
||||
}
|
||||
Self {
|
||||
classic_to_modern,
|
||||
modern_to_classic,
|
||||
known,
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace modern cubes with classic equivalents (based on cube ID)
|
||||
pub fn convert_to_classic(&self, cubes: &mut dyn std::io::Read, colours: &mut dyn std::io::Read) -> Result<Vec<u8>, ConversionError> {
|
||||
let mut cube_data = super::parser::Cube::parse_list(cubes).map_err(ConversionError::CubeParse)?;
|
||||
let colour_data = super::parser::Colour::parse_list(colours).map_err(ConversionError::ColourParse)?;
|
||||
for (i, (cube, colour)) in cube_data.iter_mut().zip(colour_data.iter()).enumerate() {
|
||||
if let Some(translation) = self.modern_to_classic.get(&cube.id) {
|
||||
let new_cube = translation.convert(cube, colour);
|
||||
*cube = new_cube;
|
||||
} else {
|
||||
return Err(ConversionError::UnknownCube {
|
||||
id: cube.id,
|
||||
index: i,
|
||||
position: (cube.x, cube.y, cube.z),
|
||||
});
|
||||
}
|
||||
}
|
||||
super::parser::Cube::dump_list(cube_data).map_err(ConversionError::CubeDump)
|
||||
}
|
||||
|
||||
/// Replace classic cubes with modern equivalents (based on cube ID)
|
||||
pub fn convert_to_modern(&self, cubes: &mut dyn std::io::Read) -> Result<ModernConversionResult, ConversionError> {
|
||||
let mut cube_data = super::parser::Cube::parse_list(cubes).map_err(ConversionError::CubeParse)?;
|
||||
let mut colour_data = Vec::with_capacity(cube_data.len());
|
||||
for (i, cube) in cube_data.iter_mut().enumerate() {
|
||||
if let Some(translation) = self.classic_to_modern.get(&cube.id) {
|
||||
let (new_cube, new_colour) = translation.convert(cube);
|
||||
log::trace!("cube {} RC15 ID {} -> Modern ID {} colour {} ({}, {}, {})", i, cube.id, new_cube.id, new_colour.colour, cube.x, cube.y, cube.z);
|
||||
*cube = new_cube;
|
||||
colour_data.push(new_colour);
|
||||
} else if !self.known.contains(&cube.id){
|
||||
return Err(ConversionError::UnknownCube {
|
||||
id: cube.id,
|
||||
index: i,
|
||||
position: (cube.x, cube.y, cube.z),
|
||||
});
|
||||
} else {
|
||||
log::debug!("No translation found for cube {} ({}, {}, {}) id {} (a valid modern ID)", i, cube.x, cube.y, cube.z, cube.id);
|
||||
colour_data.push(super::Colour {
|
||||
colour: 0,
|
||||
x: cube.x,
|
||||
y: cube.y,
|
||||
z: cube.z,
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
if cube_data.len() != colour_data.len() {
|
||||
log::error!("Emitted different lengths of cube and colour data; {} cubes != {} colours", cube_data.len(), colour_data.len());
|
||||
return Err(ConversionError::UnknownCube {
|
||||
id: 0,
|
||||
index: cube_data.len(),
|
||||
position: (0, 0, 0),
|
||||
});
|
||||
}
|
||||
let cube_bytes = super::parser::Cube::dump_list(cube_data).map_err(ConversionError::CubeDump)?;
|
||||
let colour_bytes = super::parser::Colour::dump_list(colour_data).map_err(ConversionError::ColourDump)?;
|
||||
Ok(ModernConversionResult {
|
||||
cube_data: cube_bytes,
|
||||
colour_data: colour_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Replace unknown cubes with modern equivalents (based on cube ID), skipping known cube IDs
|
||||
pub fn upgrade_to_modern(&self, cubes: &mut dyn std::io::Read, colours: &mut dyn std::io::Read) -> Result<ModernConversionResult, ConversionError> {
|
||||
let mut cube_data = super::parser::Cube::parse_list(cubes).map_err(ConversionError::CubeParse)?;
|
||||
let mut colour_data = super::parser::Colour::parse_list(colours).map_err(ConversionError::ColourParse)?;
|
||||
for (i, (cube, colour)) in cube_data.iter_mut().zip(colour_data.iter_mut()).enumerate() {
|
||||
if self.known.contains(&cube.id) { continue; }
|
||||
if let Some(translation) = self.classic_to_modern.get(&cube.id) {
|
||||
let (new_cube, new_colour) = translation.convert(cube);
|
||||
*cube = new_cube;
|
||||
*colour = new_colour;
|
||||
} else {
|
||||
return Err(ConversionError::UnknownCube {
|
||||
id: cube.id,
|
||||
index: i,
|
||||
position: (cube.x, cube.y, cube.z),
|
||||
});
|
||||
}
|
||||
}
|
||||
let cube_bytes = super::parser::Cube::dump_list(cube_data).map_err(ConversionError::CubeDump)?;
|
||||
let colour_bytes = super::parser::Colour::dump_list(colour_data).map_err(ConversionError::ColourDump)?;
|
||||
Ok(ModernConversionResult {
|
||||
cube_data: cube_bytes,
|
||||
colour_data: colour_bytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod parser;
|
||||
pub use parser::{Colour, Cube};
|
||||
|
||||
mod weapon_list;
|
||||
pub use weapon_list::WeaponListParser;
|
||||
@@ -21,6 +22,9 @@ pub use rotations::CUBE_ROTATIONS;
|
||||
mod graph;
|
||||
pub use graph::{CubeGraph, CellPoint};
|
||||
|
||||
mod conversion;
|
||||
pub use conversion::CubeConversionParser;
|
||||
|
||||
pub const CRYSTAL_ID: u32 = 3950293873;
|
||||
pub const CLASP_ID: u32 = 606866102;
|
||||
|
||||
@@ -31,6 +35,7 @@ pub struct CubeParsers {
|
||||
cpu_counter: std::sync::Arc<CpuListParser>,
|
||||
locations: std::sync::Arc<CubeLocationsParser>,
|
||||
offset: std::sync::Arc<OffsetParser>,
|
||||
converter: std::sync::Arc<CubeConversionParser>,
|
||||
}
|
||||
|
||||
impl CubeParsers {
|
||||
@@ -41,6 +46,7 @@ impl CubeParsers {
|
||||
cpu_counter: std::sync::Arc::new(CpuListParser::with_cubes(cubes.values())),
|
||||
locations: std::sync::Arc::new(CubeLocationsParser::with_cubes(cubes.values())),
|
||||
offset: std::sync::Arc::new(OffsetParser::with_cubes(cubes.values())),
|
||||
converter: std::sync::Arc::new(CubeConversionParser::with_cubes(cubes.values())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,4 +65,8 @@ impl CubeParsers {
|
||||
pub fn offset(&self) -> std::sync::Arc<OffsetParser> {
|
||||
self.offset.clone()
|
||||
}
|
||||
|
||||
pub fn converter(&self) -> std::sync::Arc<CubeConversionParser> {
|
||||
self.converter.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,17 +28,28 @@ impl CubeConfig {
|
||||
pub fn load(root: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
|
||||
let file = std::fs::File::open(root.as_ref().join(CUBE_CONFIG_FILENAME))?;
|
||||
let buffered = std::io::BufReader::new(file);
|
||||
let result = serde_json::from_reader(buffered)?;
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let filename = root.as_ref().join(format!("{}.expanded.json", CUBE_CONFIG_FILENAME.trim_end_matches(".json")));
|
||||
let file = std::fs::File::create(filename)?;
|
||||
let buffered = std::io::BufWriter::new(file);
|
||||
serde_json::to_writer_pretty(buffered, &result)?;
|
||||
let result = serde_json::from_reader::<_, Self>(buffered)?;
|
||||
let result = result.expand_with_context();
|
||||
let expanded_filename = root.as_ref().join(format!("{}.expanded.json", CUBE_CONFIG_FILENAME.trim_end_matches(".json")));
|
||||
match std::fs::File::create(&expanded_filename) {
|
||||
Ok(file) => {
|
||||
let buffered = std::io::BufWriter::new(file);
|
||||
serde_json::to_writer_pretty(buffered, &result)?;
|
||||
},
|
||||
Err(e) => {
|
||||
log::info!("Skipping creating {}: {}", expanded_filename.display(), e);
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Populate more of the config for parts of it which need more context for choosing default values
|
||||
fn expand_with_context(mut self) -> Self {
|
||||
// TODO
|
||||
self.cubes.values_mut().for_each(crate::persist::conversion::expand_conversion_data);
|
||||
self
|
||||
}
|
||||
|
||||
/// Performs configuration checks
|
||||
/// Returns true if validation succeeds, false if failed
|
||||
pub fn self_validate(&self, data_path: impl AsRef<std::path::Path>) -> bool {
|
||||
|
||||
1851
rc_core/src/persist/conversion.rs
Normal file
1851
rc_core/src/persist/conversion.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ use serde::{Serialize, Deserialize};
|
||||
|
||||
use polariton::operation::Typed;
|
||||
|
||||
use super::{WeaponData, WeaponUpgradeInfo, TechTreeData, MovementData};
|
||||
use super::{WeaponData, WeaponUpgradeInfo, TechTreeData, MovementData, CubeConversionData};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Cube {
|
||||
@@ -14,6 +14,7 @@ pub struct Cube {
|
||||
pub weapon_upgrade: Option<WeaponUpgradeInfo>,
|
||||
pub movement: Option<MovementData>,
|
||||
pub tree: Option<TechTreeData>,
|
||||
pub conversion: Option<CubeConversionData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
||||
@@ -50,6 +50,9 @@ pub use team_chooser::TeamChooser;
|
||||
mod vehicle_validator;
|
||||
pub use vehicle_validator::VehicleValidator;
|
||||
|
||||
pub(crate) mod conversion;
|
||||
pub use conversion::{CubeConversionData, FromConversionData, ToConversionData};
|
||||
|
||||
const VALID_ROBOT: &[u8] = &[64,
|
||||
0,
|
||||
0,
|
||||
|
||||
@@ -396,7 +396,7 @@ impl UserData {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn all_vehicles(&self) -> Result<Vec<oj_rc_database::schema::garage::Model>, oj_rc_database::sea_orm::DbErr> {
|
||||
pub(super) async fn all_vehicles(&self) -> Result<Vec<oj_rc_database::schema::garage::Model>, oj_rc_database::sea_orm::DbErr> {
|
||||
self.db.garages_by_user_id(self.account.id).await
|
||||
}
|
||||
|
||||
@@ -1123,7 +1123,7 @@ impl <C: Clone + Send> super::User<C> for UserData {
|
||||
let total_cost: u32 = self.garage_upgrades.increments.iter()
|
||||
.map(|x| if x.cpu > minimum_upgrade_to_cpu { 0 } else { x.cost })
|
||||
.sum();
|
||||
log::info!("Bay CPU upgrade costs {}", total_cost);
|
||||
log::debug!("Bay CPU upgrade costs {}", total_cost);
|
||||
self.currency_sub_checked(super::CurrencyType::Free, total_cost as u64).await.map_err(|e| {
|
||||
log::error!("Failed to debit user for cpu upgrade during fresh clone to slot {} for user_id {}: {}", slot, self.account.id, e);
|
||||
DATABASE_ERR
|
||||
|
||||
@@ -11,7 +11,7 @@ mod inventory;
|
||||
pub use inventory::{UnlockedParts, UnlockOverride};
|
||||
|
||||
mod traits;
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides, WebUser};
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides, WebUser, GarageWebInfo};
|
||||
|
||||
pub mod intercom;
|
||||
pub use intercom::generate_token as generate_intercom_token;
|
||||
|
||||
@@ -676,7 +676,19 @@ pub trait FactoryUser {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait WebUser: CommonUser {
|
||||
async fn garages(&self) -> Result<Vec<GarageWebInfo>, Box<dyn std::error::Error>>;
|
||||
async fn garage_by_id(&self, id: i32) -> Result<Option<VehicleData>, Box<dyn std::error::Error>>;
|
||||
async fn save_garage(&self, vehicle: crate::persist::user::VehicleData, garage_id: Option<i32>, cpu_counter: &crate::cubes::CpuListParser, weapon_orderer: &crate::cubes::WeaponListParser) -> Result<(), Box<dyn std::error::Error>>;
|
||||
async fn garage_id_selected(&self) -> Result<Option<i32>, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
pub struct GarageWebInfo {
|
||||
pub id: i32,
|
||||
pub slot: i32,
|
||||
pub total_robot_cpu: i32,
|
||||
pub bay_cpu: i32,
|
||||
pub name: String,
|
||||
pub creation_time: i64,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
||||
@@ -1,4 +1,109 @@
|
||||
#[async_trait::async_trait]
|
||||
impl super::WebUser for super::account_json::UserData {
|
||||
// TODO
|
||||
async fn garages(&self) -> Result<Vec<super::GarageWebInfo>, Box<dyn std::error::Error>> {
|
||||
let vehicles = self.all_vehicles().await?;
|
||||
Ok(vehicles.into_iter()
|
||||
.map(|v| super::GarageWebInfo {
|
||||
id: v.id,
|
||||
slot: v.slot,
|
||||
total_robot_cpu: v.total_robot_cpu,
|
||||
bay_cpu: v.bay_cpu,
|
||||
name: v.name.clone(),
|
||||
creation_time: v.creation_time,
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
async fn garage_by_id(&self, id: i32) -> Result<Option<super::VehicleData>, Box<dyn std::error::Error>> {
|
||||
let garage = self.db.garage_by_id(id).await?;
|
||||
Ok(garage.and_then(|g|
|
||||
if g.user_id == self.account.id {
|
||||
Some(super::VehicleData {
|
||||
name: Some(g.name),
|
||||
slot: g.slot,
|
||||
robot_data: g.robot_data,
|
||||
colour_data: g.colour_data,
|
||||
weapon_order: Vec::default(),
|
||||
crf_id: g.crf_id,
|
||||
was_rated: Some(g.was_rated),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
async fn save_garage(
|
||||
&self,
|
||||
vehicle: super::VehicleData,
|
||||
garage_id: Option<i32>,
|
||||
cpu_counter: &crate::cubes::CpuListParser,
|
||||
weapon_orderer: &crate::cubes::WeaponListParser,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cpu_counts = cpu_counter.calculate_cpu(&mut std::io::Cursor::new(&vehicle.robot_data));
|
||||
let weapon_order = weapon_orderer.guess_weapons(&mut std::io::Cursor::new(&vehicle.robot_data));
|
||||
let mut entity = oj_rc_database::schema::garage::ActiveModel {
|
||||
id: garage_id.map(oj_rc_database::sea_orm::ActiveValue::Set).unwrap_or(oj_rc_database::sea_orm::ActiveValue::NotSet),
|
||||
user_id: oj_rc_database::sea_orm::ActiveValue::Set(self.account.id),
|
||||
weapon_order: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::dump_csv(&weapon_order)),
|
||||
robot_data: oj_rc_database::sea_orm::ActiveValue::Set(vehicle.robot_data),
|
||||
colour_data: oj_rc_database::sea_orm::ActiveValue::Set(vehicle.colour_data),
|
||||
crf_id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
name: if let Some(new_name) = vehicle.name {
|
||||
oj_rc_database::sea_orm::ActiveValue::Set(format!("{} (imported)", new_name))
|
||||
} else {
|
||||
oj_rc_database::sea_orm::ActiveValue::Set(format!(
|
||||
"Import {}",
|
||||
garage_id.map(|x| x.to_string()).unwrap_or_else(|| "(new)".to_owned())
|
||||
))
|
||||
},
|
||||
total_robot_cpu: oj_rc_database::sea_orm::ActiveValue::Set(cpu_counts.total as _),
|
||||
total_cosmetic_cpu: oj_rc_database::sea_orm::ActiveValue::Set(cpu_counts.cosmetic as _),
|
||||
was_rated: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||
movement_categories: oj_rc_database::sea_orm::ActiveValue::Set("".to_owned()), // FIXME
|
||||
total_robot_ranking: oj_rc_database::sea_orm::ActiveValue::Set(0), // FIXME
|
||||
bay_cpu: oj_rc_database::sea_orm::ActiveValue::Set(10_000),
|
||||
//tutorial_robot: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||
mastery_level: oj_rc_database::sea_orm::ActiveValue::Set(0),
|
||||
..super::initial_data::default_reset_slot()
|
||||
};
|
||||
// TODO charge currency for new or higher CPU bay
|
||||
/*let total_cost: u32 = self.garage_upgrades.increments.iter()
|
||||
.map(|x| if x.cpu > minimum_upgrade_to_cpu { 0 } else { x.cost })
|
||||
.sum();
|
||||
log::debug!("Bay CPU upgrade costs {}", total_cost);
|
||||
self.currency_sub_checked(super::CurrencyType::Free, total_cost as u64).await.map_err(|e| {
|
||||
log::error!("Failed to debit user for cpu upgrade during create of slot {} for user_id {}: {}", slot, self.account.id, e);
|
||||
DATABASE_ERR
|
||||
})?;*/
|
||||
if let Some(garage_id) = garage_id {
|
||||
// technically we don't need an account id to get the slot
|
||||
// but this way ensures the user also owns the slot we retrieve
|
||||
// (and no slot will be retrieved if the user doesn't own the slot, even if the garage id exists)
|
||||
if let Some(slot) = self.db.slot_of_garage_by_id_and_user_id(garage_id, self.account.id).await? {
|
||||
entity.slot = oj_rc_database::sea_orm::ActiveValue::Set(slot);
|
||||
self.db.update_garage_by_user_id_and_slot(entity, self.account.id, slot).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Invalid vehicle ID {} for user {}", garage_id, self.account.id).into())
|
||||
}
|
||||
} else {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let new_slot = self.db.garage_max_slot_by_user_id(self.account.id).await? + 1;
|
||||
let uuid = super::uuid_sanitize(now);
|
||||
entity.slot = oj_rc_database::sea_orm::ActiveValue::Set(new_slot);
|
||||
entity.creation_time = oj_rc_database::sea_orm::ActiveValue::Set(now);
|
||||
entity.uuid = oj_rc_database::sea_orm::ActiveValue::Set(uuid);
|
||||
entity.thumbnail_version = oj_rc_database::sea_orm::ActiveValue::Set(0);
|
||||
entity.selected = oj_rc_database::sea_orm::ActiveValue::Set(false);
|
||||
self.db.insert_garage(entity).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn garage_id_selected(&self) -> Result<Option<i32>, Box<dyn std::error::Error>> {
|
||||
Ok(self.db.garage_selected(self.account.id).await?
|
||||
.map(|x| x.id))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user