1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Platoons social functionality (#91)

### Description

This implements and completes #87 (to the best of my knowledge)

### Game

Robocraft

### Please confirm

- [x] I am the legal owner or represent the owner of all work submitted
- [x] I consent to my submission being added to this FOSS project
- [ ] This PR used LLMs to generate some or all of the code changes

Reviewed-on: https://git.ngram.ca/OpenJam/rc-servers/pulls/91
Co-authored-by: NG (Graham) <ngniusness@gmail.com>
Co-committed-by: NG (Graham) <ngniusness@gmail.com>
This commit is contained in:
NG (Graham)
2026-02-19 01:57:03 +00:00
committed by NGnius
parent a82488ab3a
commit 50b396f34a
49 changed files with 1152 additions and 47 deletions

View File

@@ -12,6 +12,7 @@ impl ChatChannelInfo {
(Typed::Str("channelName".into()), Typed::Str(self.channel_name.clone().into())),
(Typed::Str("members".into()), Typed::Arr(Arr {
ty: polariton::serdes::TypePrefix::HashMap, // hashtable
custom_ty: None,
items: self.members.iter().map(|x| x.as_transmissible()).collect(),
})),
(Typed::Str("channelType".into()), Typed::Int(self.channel_ty as _)),

View File

@@ -32,6 +32,7 @@ impl GarageSlotInfo {
(Typed::Str("wasRated".into()), Typed::Bool(self.was_rated)),
(Typed::Str("movementCategories".into()), Typed::Arr(Arr {
ty: TypePrefix::Int, // int
custom_ty: None,
items: self.movement_categories.iter().map(|x| Typed::Int(x.but_bigger())).collect(),
})),
(Typed::Str("uniqueId1".into()), Typed::Int(self.uuid.0 as i32)),
@@ -49,6 +50,7 @@ impl GarageSlotInfo {
(Typed::Str("baySkinId".into()), Typed::Str(self.bay_skin_id.clone().into())),
(Typed::Str("weaponOrder".into()), Typed::Arr(Arr {
ty: TypePrefix::Int, // int
custom_ty: None,
items: self.weapon_order.iter().map(|x| Typed::Int(*x)).collect(),
})),
].into())
@@ -85,6 +87,7 @@ impl ControlOptions {
pub fn as_transmissible<C>(&self) -> Typed<C> {
Typed::Arr(Arr {
ty: TypePrefix::Bool, // bool
custom_ty: None,
items: vec![
Typed::Bool(self.vertical_strafing),
Typed::Bool(self.sideways_driving),

View File

@@ -21,6 +21,7 @@ impl TechTreeNode {
(Typed::Str("tp".into()), Typed::Int(self.tech_points as i32)),
(Typed::Str("neighbours".into()), Typed::Arr(Arr {
ty: TypePrefix::Str, // str
custom_ty: None,
items: self.neighbours.iter().map(|cube_id| Typed::Str(hex::encode(cube_id.to_be_bytes()).into())).collect(),
})),
].into())

View File

@@ -117,6 +117,7 @@ impl WeaponData {
let typed_arr: Vec<Typed<C>> = self.group_fire_scales.iter().map(|x| Typed::Float(*x)).collect();
out.push((Typed::Str("groupFireScales".into()), Typed::Arr(polariton::operation::Arr {
ty: polariton::serdes::TypePrefix::Float,
custom_ty: None,
items: typed_arr,
})));
}

View File

@@ -284,6 +284,7 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
fn public_channels(&self) -> Typed<C> {
Typed::Arr(polariton::operation::Arr {
ty: TypePrefix::Str,
custom_ty: None,
items: self.chat.public_channels.iter().map(|s| Typed::Str(s.into())).collect(),
})
}

View File

@@ -193,6 +193,7 @@ impl GameEventSequence {
GameEventTransmissible {
maps: Typed::Arr(polariton::operation::Arr {
ty: polariton::serdes::TypePrefix::Str,
custom_ty: None,
items: vec![
Typed::Str(crate::data::game_mode::GameMap::from_persist(item_now.singleplayer.map).as_str().into()),
Typed::Str(crate::data::game_mode::GameMap::from_persist(item_now.multiplayer.map).as_str().into()),
@@ -200,6 +201,7 @@ impl GameEventSequence {
}),
visibilities: Typed::Arr(polariton::operation::Arr {
ty: polariton::serdes::TypePrefix::Int,
custom_ty: None,
items: vec![
Typed::Int(crate::data::game_mode::MapVisibility::from_persist(item_now.singleplayer.visibility) as _),
Typed::Int(crate::data::game_mode::MapVisibility::from_persist(item_now.multiplayer.visibility) as _),
@@ -207,6 +209,7 @@ impl GameEventSequence {
}),
modes: Typed::Arr(polariton::operation::Arr {
ty: polariton::serdes::TypePrefix::Int,
custom_ty: None,
items: vec![
Typed::Int(crate::data::game_mode::GameMode::from_persist(item_now.singleplayer.mode) as _),
Typed::Int(crate::data::game_mode::GameMode::from_persist(item_now.multiplayer.mode) as _),
@@ -214,6 +217,7 @@ impl GameEventSequence {
}),
auto_heals: Typed::Arr(polariton::operation::Arr {
ty: polariton::serdes::TypePrefix::Bool,
custom_ty: None,
items: vec![
Typed::Bool(item_now.singleplayer.auto_heal),
Typed::Bool(item_now.multiplayer.auto_heal),

View File

@@ -7,6 +7,7 @@ impl super::ChatUser for UserData {
log::info!("User is subscribed to channels {:?}", channels);
Ok(polariton::operation::Typed::Arr(polariton::operation::Arr {
ty: polariton::serdes::TypePrefix::HashMap, // hashtable
custom_ty: None,
items: channels.into_iter().map(|name| crate::data::channel::ChatChannelInfo {
channel_name: name,
members: vec![
@@ -103,6 +104,7 @@ impl super::ChatUser for UserData {
})?;
Ok(polariton::operation::Typed::Arr(polariton::operation::Arr {
ty: polariton::serdes::TypePrefix::Str,
custom_ty: None,
items: sanctions.into_iter().map(|x| {
let data = crate::data::sanction::SanctionJson {
type_: crate::data::sanction::SanctionType::from_db(x.descriptor),

View File

@@ -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};
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};
pub mod intercom;
pub use intercom::generate_token as generate_intercom_token;

View File

@@ -57,7 +57,7 @@ impl super::SocialUser for UserData {
format!("Failed to retrieve friends: {}", e),
)
})?;
let friend_ids: Vec<i32> = friends.iter().map(|(_, user)| user.id).collect();
let friend_ids = friends.iter().map(|(_, user)| user.id);
let friend_avatars = self.db.user_auxs_by_user_ids_and_descriptor(friend_ids, oj_rc_database::schema::user_aux::Descriptor::AvatarId).await
.map_err(|e| {
log::error!("Failed to retrieve friend avatars for user {} : {}", self.account.id, e);
@@ -81,6 +81,37 @@ impl super::SocialUser for UserData {
)
}
async fn list_social_info(&self, public_ids: &[String]) -> Result<Vec<super::SocialInfo>, polariton_server::operations::SimpleOpError> {
let users = self.db.users_by_public_id(public_ids.iter()).await
.map_err(|e| {
log::error!("Failed to retrieve friend avatars for user {} : {}", self.account.id, e);
polariton_server::operations::SimpleOpError::with_message(
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
format!("Failed to retrieve friend avatars: {}", e),
)
})?;
let user_ids = users.iter().map(|user| user.id);
let user_avatars = self.db.user_auxs_by_user_ids_and_descriptor(user_ids, oj_rc_database::schema::user_aux::Descriptor::AvatarId).await
.map_err(|e| {
log::error!("Failed to retrieve friend avatars for user {} : {}", self.account.id, e);
polariton_server::operations::SimpleOpError::with_message(
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
format!("Failed to retrieve friend avatars: {}", e),
)
})?;
let avatar_map: std::collections::HashMap<i32, u32> = user_avatars.iter()
.filter_map(|avatar| avatar.data.parse().ok().map(|avatar_id| (avatar.user_id, avatar_id)))
.collect();
Ok(users.iter()
.map(|user| super::SocialInfo {
public_id: user.public_id.clone(),
display_name: user.display_name.clone(),
avatar_id: avatar_map.get(&user.id).and_then(|&avatar_id| if avatar_id == u32::MAX { None } else { Some(avatar_id as i32) }),
})
.collect()
)
}
async fn has_unclaimed_match_rewards(&self) -> Result<bool, polariton_server::operations::SimpleOpError> {
let count = self.db.count_score_by_user_id_and_claimed(self.account.id, false).await
.map_err(|e| {

View File

@@ -458,6 +458,7 @@ pub trait SocialUser: Send + Sync {
async fn cancel_friend(&self, username: String) -> Result<bool, polariton_server::operations::SimpleOpError>;
async fn remove_friend(&self, username: String) -> Result<bool, polariton_server::operations::SimpleOpError>;
async fn list_friends(&self) -> Result<Vec<FriendData>, polariton_server::operations::SimpleOpError>;
async fn list_social_info(&self, public_ids: &[String]) -> Result<Vec<SocialInfo>, polariton_server::operations::SimpleOpError>;
async fn has_unclaimed_match_rewards(&self) -> Result<bool, polariton_server::operations::SimpleOpError>;
async fn get_unclaimed_match_rewards(&self) -> Result<MatchRewards, polariton_server::operations::SimpleOpError>;
async fn claim_match_rewards(&self) -> Result<bool, polariton_server::operations::SimpleOpError>;
@@ -498,6 +499,13 @@ pub struct FriendData {
pub avatar_id: u32,
}
#[derive(Clone)]
pub struct SocialInfo {
pub public_id: String,
pub display_name: String,
pub avatar_id: Option<i32>,
}
pub enum FriendInviteStatus {
InviteSent,
InvitePending,