make emotions named instead, add drop prop

This commit is contained in:
Evan Carroll 2026-01-13 16:49:07 -06:00
parent 989e20757b
commit ea3b444d71
19 changed files with 1429 additions and 150 deletions

View file

@ -17,8 +17,8 @@ use tokio::sync::broadcast;
use uuid::Uuid;
use chattyness_db::{
models::{AvatarRenderData, ChannelMemberWithAvatar, User},
queries::{avatars, channel_members, realms, scenes},
models::{AvatarRenderData, ChannelMemberWithAvatar, EmotionState, User},
queries::{avatars, channel_members, loose_props, realms, scenes},
ws_messages::{ClientMessage, ServerMessage},
};
use chattyness_error::AppError;
@ -97,14 +97,15 @@ where
.await?
.ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", slug)))?;
// Verify channel (scene) exists and belongs to this realm
// Verify scene exists and belongs to this realm
// Note: Using scene_id as channel_id since channel_members uses scenes directly
let scene = scenes::get_scene_by_id(&pool, channel_id)
.await?
.ok_or_else(|| AppError::NotFound("Channel not found".to_string()))?;
.ok_or_else(|| AppError::NotFound("Scene not found".to_string()))?;
if scene.realm_id != realm.id {
return Err(AppError::NotFound(
"Channel not found in this realm".to_string(),
"Scene not found in this realm".to_string(),
));
}
@ -230,6 +231,24 @@ async fn handle_socket(
}
}
// Send loose props sync
match loose_props::list_channel_loose_props(&mut *conn, channel_id).await {
Ok(props) => {
let props_sync = ServerMessage::LoosePropsSync { props };
if let Ok(json) = serde_json::to_string(&props_sync) {
#[cfg(debug_assertions)]
tracing::debug!("[WS->Client] {}", json);
if sender.send(Message::Text(json.into())).await.is_err() {
let _ = channel_members::leave_channel(&mut *conn, channel_id, user.id).await;
return;
}
}
}
Err(e) => {
tracing::warn!("[WS] Failed to get loose props: {:?}", e);
}
}
// Broadcast join to others
let avatar = avatars::get_avatar_with_paths_conn(&mut *conn, user.id, realm_id)
.await
@ -295,15 +314,20 @@ async fn handle_socket(
});
}
ClientMessage::UpdateEmotion { emotion } => {
// We have 12 emotions (0-11)
if emotion > 11 {
continue;
}
// Parse emotion name to EmotionState
let emotion_state = match emotion.parse::<EmotionState>() {
Ok(e) => e,
Err(_) => {
#[cfg(debug_assertions)]
tracing::warn!("[WS] Invalid emotion name: {}", emotion);
continue;
}
};
let emotion_layer = match avatars::set_emotion(
&mut *recv_conn,
user_id,
realm_id,
emotion as i16,
emotion_state,
)
.await
{
@ -341,13 +365,17 @@ async fn handle_socket(
.await;
if let Ok(Some(member)) = member_info {
// Convert emotion index to name
let emotion_name = EmotionState::from_index(member.current_emotion as u8)
.map(|e| e.to_string())
.unwrap_or_else(|| "neutral".to_string());
let msg = ServerMessage::ChatMessageReceived {
message_id: Uuid::new_v4(),
user_id: Some(user_id),
guest_session_id: None,
display_name: member.display_name.clone(),
content,
emotion: member.current_emotion as u8,
emotion: emotion_name,
x: member.position_x,
y: member.position_y,
timestamp: chrono::Utc::now().timestamp_millis(),
@ -355,6 +383,76 @@ async fn handle_socket(
let _ = tx.send(msg);
}
}
ClientMessage::DropProp { inventory_item_id } => {
// Get user's current position for random offset
let member_info = channel_members::get_channel_member(
&mut *recv_conn,
channel_id,
user_id,
realm_id,
)
.await;
if let Ok(Some(member)) = member_info {
// Generate random offset (within ~50 pixels)
let offset_x = (rand::random::<f64>() - 0.5) * 100.0;
let offset_y = (rand::random::<f64>() - 0.5) * 100.0;
let pos_x = member.position_x + offset_x;
let pos_y = member.position_y + offset_y;
match loose_props::drop_prop_to_canvas(
&mut *recv_conn,
inventory_item_id,
user_id,
channel_id,
pos_x,
pos_y,
)
.await
{
Ok(prop) => {
#[cfg(debug_assertions)]
tracing::debug!(
"[WS] User {} dropped prop {} at ({}, {})",
user_id,
prop.id,
pos_x,
pos_y
);
let _ = tx.send(ServerMessage::PropDropped { prop });
}
Err(e) => {
tracing::error!("[WS] Drop prop failed: {:?}", e);
}
}
}
}
ClientMessage::PickUpProp { loose_prop_id } => {
match loose_props::pick_up_loose_prop(
&mut *recv_conn,
loose_prop_id,
user_id,
)
.await
{
Ok(_inventory_item) => {
#[cfg(debug_assertions)]
tracing::debug!(
"[WS] User {} picked up prop {}",
user_id,
loose_prop_id
);
let _ = tx.send(ServerMessage::PropPickedUp {
prop_id: loose_prop_id,
picked_up_by_user_id: Some(user_id),
picked_up_by_guest_id: None,
});
}
Err(e) => {
tracing::error!("[WS] Pick up prop failed: {:?}", e);
}
}
}
}
}
}