Implement avatar costume change

This commit is contained in:
xeon 2024-11-03 23:58:11 +03:00
parent 005f4413af
commit 4fb5d000fd
25 changed files with 622 additions and 154 deletions

1
Cargo.lock generated
View file

@ -1855,6 +1855,7 @@ dependencies = [
"common",
"sakura-data",
"sakura-message",
"sakura-persistence",
"sakura-proto",
"tracing",
]

Binary file not shown.

View file

@ -1,5 +1,6 @@
use sakura_message::output::ClientOutput;
use sakura_persistence::player_information::PlayerInformation;
use sakura_proto::PacketHead;
pub enum LogicCommand {
CreateWorld {
@ -7,7 +8,7 @@ pub enum LogicCommand {
output: ClientOutput,
},
ClientInput {
uid: u32,
head: PacketHead,
cmd_id: u16,
data: Box<[u8]>,
immediate_mode: bool,

View file

@ -161,6 +161,12 @@ pub fn sync_avatar_data(players: Res<Players>, out: Res<MessageOutput>) {
.iter()
.copied()
.collect(),
owned_costume_list: player_info
.avatar_module
.owned_costume_set
.iter()
.copied()
.collect(),
..Default::default()
},
);

View file

@ -15,7 +15,7 @@ use sakura_message::{
};
use sakura_pathfinding::PathfindingPlugin;
use sakura_persistence::{player_information::PlayerInformation, Players};
use sakura_proto::PlayerLoginRsp;
use sakura_proto::{PacketHead, PlayerLoginRsp};
use sakura_scene::{common::WorldOwnerUID, ScenePlugin};
use sakura_time::TimePlugin;
use tracing::debug;
@ -64,16 +64,20 @@ impl PlayerWorld {
app.cleanup();
app.update();
output.push(PlayerLoginRsp::default());
output.push(
sakura_proto::PacketHead::default(),
PlayerLoginRsp::default(),
);
debug!("created world for player: {uid}");
Self(app)
}
pub fn add_packet(&mut self, player_uid: u32, cmd_id: u16, data: Box<[u8]>) {
pub fn add_packet(&mut self, head: PacketHead, cmd_id: u16, data: Box<[u8]>) {
self.0
.world_mut()
.send_event(ClientMessageEvent::new(player_uid, cmd_id, data));
.send_event(ClientMessageEvent::new(head, cmd_id, data));
}
pub fn update(&mut self) {

View file

@ -4,6 +4,7 @@ use crate::{command::LogicCommand, player_world::PlayerWorld};
use common::time_util;
use sakura_message::output::ClientOutput;
use sakura_persistence::player_information::PlayerInformation;
use sakura_proto::PacketHead;
use std::sync::mpsc;
#[derive(Clone)]
@ -28,14 +29,14 @@ impl LogicSimulator {
pub fn add_client_packet(
&self,
player_uid: u32,
head: PacketHead,
cmd_id: u16,
data: Box<[u8]>,
immediate_mode: bool,
) {
self.0
.send(LogicCommand::ClientInput {
uid: player_uid,
head,
cmd_id,
data,
immediate_mode,
@ -72,14 +73,15 @@ fn simulation_loop(
);
}
ClientInput {
uid,
head,
cmd_id,
data,
immediate_mode,
} => {
let uid = head.user_id;
if let Some(world_owner_uid) = player_uid_map.get(&uid) {
if let Some(world) = player_world_map.get_mut(world_owner_uid) {
world.add_packet(uid, cmd_id, data);
world.add_packet(head, cmd_id, data);
if immediate_mode {
world.update();
}

View file

@ -55,7 +55,7 @@ pub async fn on_message(state: &'static AppState, data: Box<[u8]>) {
);
state.logic_simulator.add_client_packet(
packet.head.user_id,
packet.head.clone(),
sub_cmd.message_id as u16,
sub_cmd.body.into(),
false,
@ -67,7 +67,7 @@ pub async fn on_message(state: &'static AppState, data: Box<[u8]>) {
cmd_id => {
debug!("received packet with cmd_id: {cmd_id}");
state.logic_simulator.add_client_packet(
packet.head().user_id,
packet.head(),
cmd_id,
packet.body().into(),
true,
@ -99,9 +99,9 @@ async fn packet_sink(
state: &'static AppState,
user_id: u32,
user_session_id: u32,
mut rx: mpsc::Receiver<(u16, Box<[u8]>)>,
mut rx: mpsc::Receiver<(u16, PacketHead, Box<[u8]>)>,
) {
while let Some((cmd_id, body)) = rx.recv().await {
while let Some((cmd_id, head, body)) = rx.recv().await {
state
.gate_server_socket
.send(make_raw_packet(
@ -109,7 +109,7 @@ async fn packet_sink(
PacketHead {
user_id,
user_session_id,
..Default::default()
..head
},
&body,
))

View file

@ -2,9 +2,9 @@ use std::collections::HashMap;
use common::time_util;
use sakura_data::excel::{
avatar_excel_config_collection, avatar_flycloak_excel_config_collection,
avatar_skill_depot_excel_config_collection, weapon_excel_config_collection, AvatarExcelConfig,
AvatarUseType,
avatar_costume_excel_config_collection, avatar_excel_config_collection,
avatar_flycloak_excel_config_collection, avatar_skill_depot_excel_config_collection,
weapon_excel_config_collection, AvatarExcelConfig, AvatarUseType,
};
use sakura_persistence::player_information::*;
@ -28,6 +28,10 @@ pub fn create_default_player_information(uid: u32, nick_name: String) -> PlayerI
owned_flycloak_set: avatar_flycloak_excel_config_collection::iter()
.map(|c| c.flycloak_id)
.collect(),
owned_costume_set: avatar_costume_excel_config_collection::iter()
.filter(|c| !c.is_default)
.map(|c| c.skin_id)
.collect(),
},
item_map: HashMap::new(),
world_position: PlayerPositionInformation {
@ -129,6 +133,7 @@ fn add_avatar_and_weapon(player: &mut PlayerInformation, avatar: &AvatarExcelCon
skill_level_map,
inherent_proud_skill_list,
wearing_flycloak_id: DEFAULT_FLYCLOAK_ID,
costume_id: 0,
},
);

View file

@ -96,6 +96,7 @@ async fn handle_packet(
debug!("received packet: {}", hex::encode(&data));
let packet = RawPacket::new(&data)?;
let head = packet.head();
let (cmd_id, body) = packet::client_to_normal(packet.cmd_id(), packet.body())?;
match cmd_id {
@ -161,7 +162,7 @@ async fn handle_packet(
PacketHead {
user_session_id: session.connection.conv,
user_id: session.player_uid.get().copied().unwrap_or_default(),
..Default::default()
..head
},
&body,
))

View file

@ -1,7 +1,6 @@
use sakura_proto::{
packet::normal_to_client,
raw_packet::{make_raw_packet, RawPacket},
PacketHead,
};
use tracing::{debug, warn};
@ -14,12 +13,13 @@ pub async fn on_message(state: &'static AppState, data: Box<[u8]>) {
return;
};
let session_id = packet.head().user_session_id;
let head = packet.head();
let session_id = head.user_session_id;
if let Some(session) = state.sessions.get(&session_id) {
match normal_to_client(packet.cmd_id(), packet.body()) {
Ok((cmd_id, data)) => {
let mut data = make_raw_packet(cmd_id, PacketHead::default(), &data);
let mut data = make_raw_packet(cmd_id, head, &data);
util::xor_packet(
session.xorpad.get(),
state.initial_xorpad.as_ref(),

View file

@ -1,8 +1,11 @@
use bevy_ecs::prelude::*;
use sakura_data::excel::avatar_costume_excel_config_collection;
use sakura_entity::avatar::AvatarCostumeChangeEvent;
use sakura_message::{event::ClientMessageEvent, output::MessageOutput};
use sakura_persistence::Players;
use sakura_persistence::{player_information::PlayerInformation, Players};
use sakura_proto::{
AvatarFlycloakChangeNotify, AvatarWearFlycloakReq, AvatarWearFlycloakRsp, Retcode,
AvatarChangeCostumeReq, AvatarChangeCostumeRsp, AvatarFlycloakChangeNotify,
AvatarWearFlycloakReq, AvatarWearFlycloakRsp, Retcode,
};
use tracing::{debug, instrument};
@ -11,45 +14,135 @@ pub fn handle_appearance_change_request(
mut events: EventReader<ClientMessageEvent>,
mut players: ResMut<Players>,
message_output: Res<MessageOutput>,
mut costume_change_events: EventWriter<AvatarCostumeChangeEvent>,
) {
for message in events.read() {
if let Some(request) = message.decode::<AvatarWearFlycloakReq>() {
let player = players.get_mut(message.sender_uid());
let mut rsp = AvatarWearFlycloakRsp::default();
if player
.avatar_module
.owned_flycloak_set
.contains(&request.flycloak_id)
{
if let Some(avatar) = player
.avatar_module
.avatar_map
.get_mut(&request.avatar_guid)
{
rsp.avatar_guid = request.avatar_guid;
rsp.flycloak_id = request.flycloak_id;
avatar.wearing_flycloak_id = request.flycloak_id;
debug!(
"wear flycloak_id: {}, avatar_guid: {}",
request.flycloak_id, request.avatar_guid
);
if let Some(notify) = wear_flycloak(player, request, &mut rsp) {
message_output.send_to_all(notify);
}
message_output.send_to_all(AvatarFlycloakChangeNotify {
avatar_guid: request.avatar_guid,
flycloak_id: request.flycloak_id,
});
} else {
debug!("avatar with guid {} not found", request.avatar_guid);
rsp.retcode = Retcode::RetCanNotFindAvatar.into();
}
} else {
debug!("flycloak id {} is not owned", request.flycloak_id);
rsp.retcode = Retcode::RetNotHasFlycloak.into();
message_output.send(message.sender_uid(), rsp);
} else if let Some(request) = message.decode::<AvatarChangeCostumeReq>() {
let player = players.get_mut(message.sender_uid());
let mut rsp = AvatarChangeCostumeRsp::default();
if let Some(change_event) = change_costume(player, request, &mut rsp) {
costume_change_events.send(change_event);
}
message_output.send(message.sender_uid(), rsp);
}
}
}
#[instrument(skip(player, response))]
fn wear_flycloak(
player: &mut PlayerInformation,
request: AvatarWearFlycloakReq,
response: &mut AvatarWearFlycloakRsp,
) -> Option<AvatarFlycloakChangeNotify> {
if !player
.avatar_module
.owned_flycloak_set
.contains(&request.flycloak_id)
{
debug!("flycloak id {} is not owned", request.flycloak_id);
response.retcode = Retcode::RetNotHasFlycloak.into();
return None;
}
let Some(avatar) = player
.avatar_module
.avatar_map
.get_mut(&request.avatar_guid)
else {
debug!("avatar with guid {} not found", request.avatar_guid);
response.retcode = Retcode::RetCanNotFindAvatar.into();
return None;
};
response.avatar_guid = request.avatar_guid;
response.flycloak_id = request.flycloak_id;
avatar.wearing_flycloak_id = request.flycloak_id;
debug!(
"wear flycloak_id: {}, avatar_guid: {}",
request.flycloak_id, request.avatar_guid
);
Some(AvatarFlycloakChangeNotify {
avatar_guid: request.avatar_guid,
flycloak_id: request.flycloak_id,
})
}
#[instrument(skip(player, response))]
fn change_costume(
player: &mut PlayerInformation,
request: AvatarChangeCostumeReq,
response: &mut AvatarChangeCostumeRsp,
) -> Option<AvatarCostumeChangeEvent> {
response.retcode = Retcode::RetFail.into();
let config = (request.costume_id != 0)
.then(|| {
avatar_costume_excel_config_collection::iter().find(|c| c.skin_id == request.costume_id)
})
.flatten();
if request.costume_id != 0 && config.is_none() {
debug!("costume_id {} config doesn't exist", request.costume_id);
return None;
};
if !player
.avatar_module
.owned_costume_set
.contains(&request.costume_id)
&& config.is_some()
{
debug!("costume is not unlocked, id: {}", request.costume_id);
response.retcode = Retcode::RetNotHasCostume.into();
return None;
}
let Some(avatar) = player
.avatar_module
.avatar_map
.get_mut(&request.avatar_guid)
else {
debug!("avatar guid {} doesn't exist", request.avatar_guid);
return None;
};
if let Some(config) = config {
if config.character_id != avatar.avatar_id {
debug!(
"avatar costume mismatch, config: {}, requested: {}",
config.character_id, avatar.avatar_id
);
response.retcode = Retcode::RetCostumeAvatarError.into();
return None;
}
}
response.avatar_guid = request.avatar_guid;
response.costume_id = request.costume_id;
response.retcode = Retcode::RetSucc.into();
avatar.costume_id = request.costume_id;
debug!(
"change costume for avatar {} to {}",
avatar.avatar_id, request.costume_id
);
Some(AvatarCostumeChangeEvent(
player.uid,
request.avatar_guid,
request.costume_id,
))
}

View file

@ -0,0 +1,31 @@
use sakura_data_derive::FromBinary;
#[derive(Debug, FromBinary)]
pub struct AvatarCostumeExcelConfig {
pub skin_id: u32,
pub index_id: u32,
pub name: u32,
pub desc: u32,
pub item_id: u32,
pub character_id: u32,
pub json_name: String,
pub prefab_path_hash: u64,
pub prefab_remote_path_hash: u64,
pub prefab_npc_path_hash: u64,
pub animator_config_path_hash: u64,
pub prefab_manekin_path_hash: u64,
pub unk_1: u64,
pub controller_path_hash: u64,
pub controller_remote_path_hash: u64,
pub is_default: bool,
pub is_default_unlock: bool,
pub quality: u32,
pub hide: bool,
pub front_icon_name: String,
pub side_icon_name: String,
pub image_name_hash: u64,
pub domestic_hide_in_art_preview: bool,
pub oversea_hide_in_art_preview: bool,
pub unk_2: u64,
pub unk_3: u64,
}

View file

@ -1,3 +1,4 @@
mod avatar_costume_excel_config;
mod avatar_curve_excel_config;
mod avatar_excel_config;
mod avatar_flycloak_excel_config;
@ -11,6 +12,7 @@ mod weapon_curve_excel_config;
mod weapon_excel_config;
pub mod common;
pub use avatar_costume_excel_config::*;
pub use avatar_curve_excel_config::*;
pub use avatar_excel_config::*;
pub use avatar_flycloak_excel_config::*;
@ -65,6 +67,7 @@ macro_rules! excel_loader {
excel_loader! {
AvatarExcelConfig;
AvatarCostumeExcelConfig;
AvatarFlycloakExcelConfig;
AvatarSkillDepotExcelConfig;
AvatarCurveExcelConfig;

View file

@ -79,6 +79,7 @@ mod tests {
excel_test!(
AvatarExcelConfig,
AvatarCostumeExcelConfig,
AvatarFlycloakExcelConfig,
AvatarSkillDepotExcelConfig,
AvatarPromoteExcelConfig,

View file

@ -11,5 +11,6 @@ tracing.workspace = true
common.workspace = true
sakura-data.workspace = true
sakura-persistence.workspace = true
sakura-message.workspace = true
sakura-proto.workspace = true

View file

@ -2,8 +2,17 @@ use std::collections::HashMap;
use bevy_ecs::{prelude::*, query::QueryData};
use sakura_message::output::MessageOutput;
use sakura_persistence::{
player_information::{AvatarInformation, ItemInformation},
Players,
};
use sakura_proto::{AvatarChangeCostumeNotify, SceneEntityInfo};
use crate::{int_prop_pair, transform::Transform, weapon::WeaponQueryReadOnly};
use crate::{
int_prop_pair,
transform::Transform,
weapon::{WeaponQueryReadOnly, WeaponQueryReadOnlyItem},
};
use super::{ability::Ability, common::*};
@ -25,6 +34,9 @@ pub struct AvatarEquipChangeEvent {
pub weapon_guid: u64,
}
#[derive(Event)]
pub struct AvatarCostumeChangeEvent(pub u32, pub u64, pub u32);
#[derive(Component)]
pub struct AvatarID(pub u32);
@ -93,6 +105,52 @@ pub struct AvatarQueryReadOnly {
pub inherent_proud_skill_list: &'static InherentProudSkillList,
}
pub fn update_avatar_appearance(
mut events: EventReader<AvatarCostumeChangeEvent>,
mut avatars: Query<(&Guid, &mut AvatarAppearance)>,
) {
for AvatarCostumeChangeEvent(_, guid, costume_id) in events.read() {
if let Some((_, mut appearance)) = avatars.iter_mut().find(|(g, _)| g.0 == *guid) {
appearance.costume_id = *costume_id;
}
}
}
pub fn notify_avatar_costume_change(
mut events: EventReader<AvatarCostumeChangeEvent>,
avatars: Query<AvatarQueryReadOnly>,
weapons: Query<WeaponQueryReadOnly>,
message_output: Res<MessageOutput>,
players: Res<Players>,
) {
for AvatarCostumeChangeEvent(player_uid, guid, _) in events.read() {
if let Some(avatar_data) = avatars
.iter()
.find(|avatar_data| avatar_data.guid.0 == *guid)
{
let weapon_data = weapons.get(avatar_data.equipment.weapon).unwrap();
message_output.send_to_all(AvatarChangeCostumeNotify {
entity_info: Some(build_avatar_entity_info(&avatar_data, &weapon_data)),
});
}
// that's disgusting, this packet required even if avatar is not on scene
// even though it contains SceneEntityInfo
else {
let player = players.get(*player_uid);
let avatar = player.avatar_module.avatar_map.get(guid).unwrap();
let weapon = player.item_map.get(&avatar.weapon_guid).unwrap();
message_output.send(
*player_uid,
AvatarChangeCostumeNotify {
entity_info: Some(build_fake_avatar_entity_info(avatar, weapon)),
},
);
}
}
}
pub fn notify_appear_avatar_entities(
appear_avatars: Query<AvatarQueryReadOnly, (Added<Visible>, Without<ToBeRemovedMarker>)>,
weapons: Query<WeaponQueryReadOnly>,
@ -107,83 +165,7 @@ pub fn notify_appear_avatar_entities(
.iter()
.map(|avatar_data| {
let weapon_data = weapons.get(avatar_data.equipment.weapon).unwrap();
SceneEntityInfo {
entity_type: ProtEntityType::Avatar.into(),
entity_id: avatar_data.entity_id.0,
name: String::new(),
motion_info: Some(MotionInfo {
pos: Some(avatar_data.transform.position.into()),
rot: Some(avatar_data.transform.rotation.into()),
speed: Some(Vector::default()),
..Default::default()
}),
prop_list: vec![
int_prop_pair!(PROP_LEVEL, avatar_data.level.0),
int_prop_pair!(PROP_BREAK_LEVEL, avatar_data.break_level.0),
],
fight_prop_list: avatar_data
.fight_properties
.0
.iter()
.map(|(k, v)| FightPropPair {
prop_type: *k as u32,
prop_value: *v,
})
.collect(),
life_state: *avatar_data.life_state as u32,
animator_para_list: vec![AnimatorParameterValueInfoPair {
name_id: 0,
animator_para: Some(AnimatorParameterValueInfo::default()),
}],
last_move_scene_time_ms: 0,
last_move_reliable_seq: 0,
entity_client_data: Some(EntityClientData::default()),
entity_environment_info_list: Vec::with_capacity(0),
entity_authority_info: Some(EntityAuthorityInfo {
ability_info: Some(AbilitySyncStateInfo::default()),
born_pos: Some(Vector::default()),
client_extra_info: Some(EntityClientExtraInfo {
skill_anchor_position: Some(Vector::default()),
}),
..Default::default()
}),
tag_list: Vec::with_capacity(0),
server_buff_list: Vec::with_capacity(0),
entity: Some(scene_entity_info::Entity::Avatar(SceneAvatarInfo {
uid: avatar_data.owner_player_uid.0,
avatar_id: avatar_data.avatar_id.0,
guid: avatar_data.guid.0,
peer_id: avatar_data.control_peer.0,
equip_id_list: vec![weapon_data.weapon_id.0],
skill_depot_id: avatar_data.skill_depot.0,
talent_id_list: vec![],
weapon: Some(SceneWeaponInfo {
guid: weapon_data.guid.0,
entity_id: weapon_data.entity_id.0,
gadget_id: weapon_data.gadget_id.0,
item_id: weapon_data.weapon_id.0,
level: weapon_data.level.0,
promote_level: weapon_data.promote_level.0,
affix_map: weapon_data.affix_map.0.clone(),
ability_info: Some(AbilitySyncStateInfo::default()),
renderer_changed_info: Some(EntityRendererChangedInfo::default()),
}),
reliquary_list: Vec::with_capacity(0),
core_proud_skill_level: 0,
inherent_proud_skill_list: avatar_data.inherent_proud_skill_list.0.clone(),
skill_level_map: avatar_data.skill_level_map.0.clone(),
proud_skill_extra_level_map: HashMap::with_capacity(0),
server_buff_list: Vec::with_capacity(0),
team_resonance_list: Vec::with_capacity(0),
wearing_flycloak_id: avatar_data.appearance.flycloak_id,
born_time: avatar_data.born_time.0,
costume_id: avatar_data.appearance.costume_id,
cur_vehicle_info: None,
excel_info: Some(AvatarExcelInfo::default()),
anim_hash: 0,
})),
}
build_avatar_entity_info(&avatar_data, &weapon_data)
})
.collect(),
});
@ -194,3 +176,138 @@ pub fn run_if_avatar_entities_appeared(
) -> bool {
!appear_avatars.is_empty()
}
fn build_fake_avatar_entity_info(
avatar: &AvatarInformation,
weapon: &ItemInformation,
) -> SceneEntityInfo {
use sakura_proto::*;
let ItemInformation::Weapon {
weapon_id,
level,
promote_level,
affix_map,
..
} = weapon;
SceneEntityInfo {
entity_type: ProtEntityType::Avatar.into(),
entity_id: 0,
entity: Some(scene_entity_info::Entity::Avatar(SceneAvatarInfo {
uid: (avatar.guid >> 32) as u32,
avatar_id: avatar.avatar_id,
guid: avatar.guid,
equip_id_list: vec![*weapon_id],
skill_depot_id: avatar.skill_depot_id,
talent_id_list: vec![],
weapon: Some(SceneWeaponInfo {
guid: avatar.weapon_guid,
item_id: *weapon_id,
level: *level,
promote_level: *promote_level,
affix_map: affix_map.clone(),
..Default::default()
}),
reliquary_list: Vec::with_capacity(0),
core_proud_skill_level: 0,
inherent_proud_skill_list: avatar.inherent_proud_skill_list.clone(),
skill_level_map: avatar.skill_level_map.clone(),
proud_skill_extra_level_map: HashMap::with_capacity(0),
server_buff_list: Vec::with_capacity(0),
team_resonance_list: Vec::with_capacity(0),
wearing_flycloak_id: avatar.wearing_flycloak_id,
born_time: avatar.born_time,
costume_id: avatar.costume_id,
cur_vehicle_info: None,
excel_info: Some(AvatarExcelInfo::default()),
anim_hash: 0,
..Default::default()
})),
..Default::default()
}
}
fn build_avatar_entity_info(
avatar_data: &AvatarQueryReadOnlyItem,
weapon_data: &WeaponQueryReadOnlyItem,
) -> SceneEntityInfo {
use sakura_proto::*;
SceneEntityInfo {
entity_type: ProtEntityType::Avatar.into(),
entity_id: avatar_data.entity_id.0,
name: String::new(),
motion_info: Some(MotionInfo {
pos: Some(avatar_data.transform.position.into()),
rot: Some(avatar_data.transform.rotation.into()),
speed: Some(Vector::default()),
..Default::default()
}),
prop_list: vec![
int_prop_pair!(PROP_LEVEL, avatar_data.level.0),
int_prop_pair!(PROP_BREAK_LEVEL, avatar_data.break_level.0),
],
fight_prop_list: avatar_data
.fight_properties
.0
.iter()
.map(|(k, v)| FightPropPair {
prop_type: *k as u32,
prop_value: *v,
})
.collect(),
life_state: *avatar_data.life_state as u32,
animator_para_list: vec![AnimatorParameterValueInfoPair {
name_id: 0,
animator_para: Some(AnimatorParameterValueInfo::default()),
}],
last_move_scene_time_ms: 0,
last_move_reliable_seq: 0,
entity_client_data: Some(EntityClientData::default()),
entity_environment_info_list: Vec::with_capacity(0),
entity_authority_info: Some(EntityAuthorityInfo {
ability_info: Some(AbilitySyncStateInfo::default()),
born_pos: Some(Vector::default()),
client_extra_info: Some(EntityClientExtraInfo {
skill_anchor_position: Some(Vector::default()),
}),
..Default::default()
}),
tag_list: Vec::with_capacity(0),
server_buff_list: Vec::with_capacity(0),
entity: Some(scene_entity_info::Entity::Avatar(SceneAvatarInfo {
uid: avatar_data.owner_player_uid.0,
avatar_id: avatar_data.avatar_id.0,
guid: avatar_data.guid.0,
peer_id: avatar_data.control_peer.0,
equip_id_list: vec![weapon_data.weapon_id.0],
skill_depot_id: avatar_data.skill_depot.0,
talent_id_list: vec![],
weapon: Some(SceneWeaponInfo {
guid: weapon_data.guid.0,
entity_id: weapon_data.entity_id.0,
gadget_id: weapon_data.gadget_id.0,
item_id: weapon_data.weapon_id.0,
level: weapon_data.level.0,
promote_level: weapon_data.promote_level.0,
affix_map: weapon_data.affix_map.0.clone(),
ability_info: Some(AbilitySyncStateInfo::default()),
renderer_changed_info: Some(EntityRendererChangedInfo::default()),
}),
reliquary_list: Vec::with_capacity(0),
core_proud_skill_level: 0,
inherent_proud_skill_list: avatar_data.inherent_proud_skill_list.0.clone(),
skill_level_map: avatar_data.skill_level_map.0.clone(),
proud_skill_extra_level_map: HashMap::with_capacity(0),
server_buff_list: Vec::with_capacity(0),
team_resonance_list: Vec::with_capacity(0),
wearing_flycloak_id: avatar_data.appearance.flycloak_id,
born_time: avatar_data.born_time.0,
costume_id: avatar_data.appearance.costume_id,
cur_vehicle_info: None,
excel_info: Some(AvatarExcelInfo::default()),
anim_hash: 0,
})),
}
}

View file

@ -1,4 +1,4 @@
use avatar::AvatarEquipChangeEvent;
use avatar::{AvatarCostumeChangeEvent, AvatarEquipChangeEvent};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use common::{EntityCounter, FightProperties, LifeState, ProtocolEntityID, ToBeRemovedMarker};
@ -25,7 +25,9 @@ impl Plugin for EntityPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(EntityCounter::default())
.add_event::<AvatarEquipChangeEvent>()
.add_event::<AvatarCostumeChangeEvent>()
.add_event::<EntityDisappearEvent>()
.add_systems(Update, avatar::update_avatar_appearance)
.add_systems(
Last,
(
@ -33,6 +35,7 @@ impl Plugin for EntityPlugin {
notify_life_state_change,
notify_disappear_entities,
remove_marked_entities,
avatar::notify_avatar_costume_change,
avatar::notify_appear_avatar_entities
.run_if(avatar::run_if_avatar_entities_appeared),
monster::notify_appear_monster_entities

View file

@ -1,16 +1,20 @@
use bevy_ecs::prelude::*;
use sakura_proto::YSMessage;
use sakura_proto::{PacketHead, YSMessage};
#[derive(Event)]
pub struct ClientMessageEvent(u32, u16, Box<[u8]>);
pub struct ClientMessageEvent(PacketHead, u16, Box<[u8]>);
impl ClientMessageEvent {
pub fn new(uid: u32, cmd_id: u16, data: Box<[u8]>) -> Self {
Self(uid, cmd_id, data)
pub fn new(head: PacketHead, cmd_id: u16, data: Box<[u8]>) -> Self {
Self(head, cmd_id, data)
}
pub const fn sender_uid(&self) -> u32 {
self.0
self.0.user_id
}
pub const fn head(&self) -> &PacketHead {
&self.0
}
pub const fn cmd_id(&self) -> u16 {

View file

@ -1,11 +1,11 @@
use std::collections::HashMap;
use bevy_ecs::system::Resource;
use sakura_proto::YSMessage;
use sakura_proto::{PacketHead, YSMessage};
use tokio::sync::mpsc;
#[derive(Clone)]
pub struct ClientOutput(mpsc::Sender<(u16, Box<[u8]>)>);
pub struct ClientOutput(mpsc::Sender<(u16, PacketHead, Box<[u8]>)>);
#[derive(Resource)]
pub struct MessageOutput(HashMap<u32, ClientOutput>);
@ -17,26 +17,27 @@ impl MessageOutput {
pub fn send(&self, player_uid: u32, message: impl YSMessage) {
if let Some(out) = self.0.get(&player_uid) {
out.push(message);
out.push(PacketHead::default(), message);
}
}
pub fn send_to_all(&self, message: impl YSMessage + Clone) {
for out in self.0.values() {
out.push(message.clone());
out.push(PacketHead::default(), message.clone());
}
}
}
impl ClientOutput {
pub fn new(tx: mpsc::Sender<(u16, Box<[u8]>)>) -> Self {
pub fn new(tx: mpsc::Sender<(u16, PacketHead, Box<[u8]>)>) -> Self {
Self(tx)
}
pub fn push(&self, message: impl YSMessage) {
pub fn push(&self, head: PacketHead, message: impl YSMessage) {
self.0
.blocking_send((
message.get_cmd_id(),
head,
message.encode_to_vec().into_boxed_slice(),
))
.unwrap()

View file

@ -34,6 +34,7 @@ pub struct AvatarModuleInformation {
pub avatar_map: HashMap<u64, AvatarInformation>,
pub team_map: HashMap<u32, AvatarTeamInformation>,
pub owned_flycloak_set: HashSet<u32>,
pub owned_costume_set: HashSet<u32>,
}
#[derive(Serialize, Deserialize)]
@ -55,6 +56,7 @@ pub struct AvatarInformation {
pub skill_level_map: HashMap<u32, u32>,
pub inherent_proud_skill_list: Vec<u32>,
pub wearing_flycloak_id: u32,
pub costume_id: u32,
}
#[derive(Serialize, Deserialize)]

View file

@ -4128,11 +4128,11 @@ pub struct ForceUpdateInfo {
#[derive(sakura_proto_derive::CmdID)]
#[cmdid(22470)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct Flbhgdcpebg {
pub struct ClientLoadingCostumeVerificationNotify {
#[prost(uint32, tag = "6")]
pub costume_id: u32,
#[prost(uint64, tag = "3")]
pub dljodpnohgd: u64,
pub prefab_hash: u64,
#[prost(uint64, tag = "5")]
pub guid: u64,
}
@ -7044,11 +7044,11 @@ pub struct AvatarDataNotify {
#[prost(uint32, repeated, tag = "2")]
pub backup_avatar_team_order_list: ::prost::alloc::vec::Vec<u32>,
#[prost(uint32, repeated, tag = "3")]
pub ophdokofiho: ::prost::alloc::vec::Vec<u32>,
pub owned_costume_list: ::prost::alloc::vec::Vec<u32>,
#[prost(uint64, tag = "4")]
pub choose_avatar_guid: u64,
#[prost(uint32, repeated, tag = "5")]
pub cpeajkcgpio: ::prost::alloc::vec::Vec<u32>,
pub owned_trace_effect_list: ::prost::alloc::vec::Vec<u32>,
#[prost(map = "uint32, message", tag = "6")]
pub avatar_team_map: ::std::collections::HashMap<u32, AvatarTeam>,
#[prost(message, repeated, tag = "9")]
@ -20156,9 +20156,9 @@ pub mod gadget_play_info {
#[derive(sakura_proto_derive::CmdID)]
#[cmdid(24457)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Coocgbbbiio {
pub struct AvatarChangeCostumeNotify {
#[prost(message, optional, tag = "11")]
pub ekdkeobgnmm: ::core::option::Option<SceneEntityInfo>,
pub entity_info: ::core::option::Option<SceneEntityInfo>,
}
#[derive(sakura_proto_derive::CmdID)]
#[cmdid(3411)]
@ -25399,7 +25399,7 @@ pub struct SceneForceUnlockNotify {
#[derive(sakura_proto_derive::CmdID)]
#[cmdid(5695)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct Pbmdicgahkn {
pub struct HomeAvatarCostumeChangeNotify {
#[prost(uint32, tag = "5")]
pub costume_id: u32,
#[prost(uint32, tag = "14")]
@ -27272,9 +27272,9 @@ pub struct Abmcjlmomkg {
#[derive(sakura_proto_derive::CmdID)]
#[cmdid(27168)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Ehiogclpann {
pub struct AvatarChangeTraceEffectNotify {
#[prost(message, optional, tag = "4")]
pub ekdkeobgnmm: ::core::option::Option<SceneEntityInfo>,
pub entity_info: ::core::option::Option<SceneEntityInfo>,
}
#[derive(sakura_proto_derive::CmdID)]
#[cmdid(25876)]
@ -41994,7 +41994,7 @@ pub struct Ieokmefncfg {
#[derive(sakura_proto_derive::CmdID)]
#[cmdid(20708)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct Anagafpkcki {
pub struct AvatarChangeCostumeReq {
#[prost(uint32, tag = "13")]
pub costume_id: u32,
#[prost(uint64, tag = "10")]
@ -50336,7 +50336,7 @@ pub struct Ffajmhiamje {
#[derive(sakura_proto_derive::CmdID)]
#[cmdid(25161)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct Beggfkckgif {
pub struct AvatarChangeCostumeRsp {
#[prost(uint32, tag = "3")]
pub costume_id: u32,
#[prost(uint64, tag = "14")]

View file

@ -289,6 +289,17 @@ impl From<crate::client::ForceUpdateInfo> for ForceUpdateInfo {
}
}
}
impl From<crate::client::ClientLoadingCostumeVerificationNotify>
for ClientLoadingCostumeVerificationNotify {
fn from(value: crate::client::ClientLoadingCostumeVerificationNotify) -> Self {
Self {
costume_id: value.costume_id.into(),
prefab_hash: value.prefab_hash.into(),
guid: value.guid.into(),
..Default::default()
}
}
}
impl From<crate::client::AvatarTeam> for AvatarTeam {
fn from(value: crate::client::AvatarTeam) -> Self {
Self {
@ -419,6 +430,11 @@ impl From<crate::client::AvatarDataNotify> for AvatarDataNotify {
.into_iter()
.map(|v| v.into())
.collect(),
owned_costume_list: value
.owned_costume_list
.into_iter()
.map(|v| v.into())
.collect(),
choose_avatar_guid: value.choose_avatar_guid.into(),
avatar_team_map: value
.avatar_team_map
@ -1250,6 +1266,14 @@ impl From<crate::client::GadgetPlayInfo> for GadgetPlayInfo {
}
}
}
impl From<crate::client::AvatarChangeCostumeNotify> for AvatarChangeCostumeNotify {
fn from(value: crate::client::AvatarChangeCostumeNotify) -> Self {
Self {
entity_info: value.entity_info.map(|v| v.into()),
..Default::default()
}
}
}
impl From<crate::client::Material> for Material {
fn from(value: crate::client::Material) -> Self {
Self {
@ -1567,6 +1591,16 @@ impl From<crate::client::SceneForceUnlockNotify> for SceneForceUnlockNotify {
}
}
}
impl From<crate::client::HomeAvatarCostumeChangeNotify>
for HomeAvatarCostumeChangeNotify {
fn from(value: crate::client::HomeAvatarCostumeChangeNotify) -> Self {
Self {
costume_id: value.costume_id.into(),
avatar_id: value.avatar_id.into(),
..Default::default()
}
}
}
impl From<crate::client::AiSyncInfo> for AiSyncInfo {
fn from(value: crate::client::AiSyncInfo) -> Self {
Self {
@ -2410,6 +2444,15 @@ impl From<crate::client::MassivePropSyncInfo> for MassivePropSyncInfo {
}
}
}
impl From<crate::client::AvatarChangeCostumeReq> for AvatarChangeCostumeReq {
fn from(value: crate::client::AvatarChangeCostumeReq) -> Self {
Self {
costume_id: value.costume_id.into(),
avatar_guid: value.avatar_guid.into(),
..Default::default()
}
}
}
impl From<crate::client::BreakoutVector2> for BreakoutVector2 {
fn from(value: crate::client::BreakoutVector2) -> Self {
Self {
@ -2649,3 +2692,13 @@ impl From<crate::client::SetUpAvatarTeamRsp> for SetUpAvatarTeamRsp {
}
}
}
impl From<crate::client::AvatarChangeCostumeRsp> for AvatarChangeCostumeRsp {
fn from(value: crate::client::AvatarChangeCostumeRsp) -> Self {
Self {
costume_id: value.costume_id.into(),
avatar_guid: value.avatar_guid.into(),
retcode: value.retcode.into(),
..Default::default()
}
}
}

View file

@ -94,6 +94,17 @@ pub fn client_to_normal(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::ClientLoadingCostumeVerificationNotify::CMD_ID => {
let proto = crate::client::ClientLoadingCostumeVerificationNotify::decode(
body,
)?;
let proto: crate::normal::ClientLoadingCostumeVerificationNotify = proto
.into();
Ok((
crate::normal::ClientLoadingCostumeVerificationNotify::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::AvatarWearFlycloakRsp::CMD_ID => {
let proto = crate::client::AvatarWearFlycloakRsp::decode(body)?;
let proto: crate::normal::AvatarWearFlycloakRsp = proto.into();
@ -302,6 +313,14 @@ pub fn client_to_normal(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::AvatarChangeCostumeNotify::CMD_ID => {
let proto = crate::client::AvatarChangeCostumeNotify::decode(body)?;
let proto: crate::normal::AvatarChangeCostumeNotify = proto.into();
Ok((
crate::normal::AvatarChangeCostumeNotify::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::EvtAvatarLockChairRsp::CMD_ID => {
let proto = crate::client::EvtAvatarLockChairRsp::decode(body)?;
let proto: crate::normal::EvtAvatarLockChairRsp = proto.into();
@ -366,6 +385,14 @@ pub fn client_to_normal(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::HomeAvatarCostumeChangeNotify::CMD_ID => {
let proto = crate::client::HomeAvatarCostumeChangeNotify::decode(body)?;
let proto: crate::normal::HomeAvatarCostumeChangeNotify = proto.into();
Ok((
crate::normal::HomeAvatarCostumeChangeNotify::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::ScenePointUnlockNotify::CMD_ID => {
let proto = crate::client::ScenePointUnlockNotify::decode(body)?;
let proto: crate::normal::ScenePointUnlockNotify = proto.into();
@ -502,6 +529,14 @@ pub fn client_to_normal(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::AvatarChangeCostumeReq::CMD_ID => {
let proto = crate::client::AvatarChangeCostumeReq::decode(body)?;
let proto: crate::normal::AvatarChangeCostumeReq = proto.into();
Ok((
crate::normal::AvatarChangeCostumeReq::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::PlayerEnterSceneNotify::CMD_ID => {
let proto = crate::client::PlayerEnterSceneNotify::decode(body)?;
let proto: crate::normal::PlayerEnterSceneNotify = proto.into();
@ -566,6 +601,14 @@ pub fn client_to_normal(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::client::AvatarChangeCostumeRsp::CMD_ID => {
let proto = crate::client::AvatarChangeCostumeRsp::decode(body)?;
let proto: crate::normal::AvatarChangeCostumeRsp = proto.into();
Ok((
crate::normal::AvatarChangeCostumeRsp::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
not_found => Err(ProtocolConversionError::NotFound(not_found)),
}
}
@ -665,6 +708,17 @@ pub fn normal_to_client(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::ClientLoadingCostumeVerificationNotify::CMD_ID => {
let proto = crate::normal::ClientLoadingCostumeVerificationNotify::decode(
body,
)?;
let proto: crate::client::ClientLoadingCostumeVerificationNotify = proto
.into();
Ok((
crate::client::ClientLoadingCostumeVerificationNotify::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::AvatarWearFlycloakRsp::CMD_ID => {
let proto = crate::normal::AvatarWearFlycloakRsp::decode(body)?;
let proto: crate::client::AvatarWearFlycloakRsp = proto.into();
@ -873,6 +927,14 @@ pub fn normal_to_client(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::AvatarChangeCostumeNotify::CMD_ID => {
let proto = crate::normal::AvatarChangeCostumeNotify::decode(body)?;
let proto: crate::client::AvatarChangeCostumeNotify = proto.into();
Ok((
crate::client::AvatarChangeCostumeNotify::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::EvtAvatarLockChairRsp::CMD_ID => {
let proto = crate::normal::EvtAvatarLockChairRsp::decode(body)?;
let proto: crate::client::EvtAvatarLockChairRsp = proto.into();
@ -937,6 +999,14 @@ pub fn normal_to_client(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::HomeAvatarCostumeChangeNotify::CMD_ID => {
let proto = crate::normal::HomeAvatarCostumeChangeNotify::decode(body)?;
let proto: crate::client::HomeAvatarCostumeChangeNotify = proto.into();
Ok((
crate::client::HomeAvatarCostumeChangeNotify::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::ScenePointUnlockNotify::CMD_ID => {
let proto = crate::normal::ScenePointUnlockNotify::decode(body)?;
let proto: crate::client::ScenePointUnlockNotify = proto.into();
@ -1073,6 +1143,14 @@ pub fn normal_to_client(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::AvatarChangeCostumeReq::CMD_ID => {
let proto = crate::normal::AvatarChangeCostumeReq::decode(body)?;
let proto: crate::client::AvatarChangeCostumeReq = proto.into();
Ok((
crate::client::AvatarChangeCostumeReq::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::PlayerEnterSceneNotify::CMD_ID => {
let proto = crate::normal::PlayerEnterSceneNotify::decode(body)?;
let proto: crate::client::PlayerEnterSceneNotify = proto.into();
@ -1137,6 +1215,14 @@ pub fn normal_to_client(
proto.encode_to_vec().into_boxed_slice(),
))
}
crate::normal::AvatarChangeCostumeRsp::CMD_ID => {
let proto = crate::normal::AvatarChangeCostumeRsp::decode(body)?;
let proto: crate::client::AvatarChangeCostumeRsp = proto.into();
Ok((
crate::client::AvatarChangeCostumeRsp::CMD_ID,
proto.encode_to_vec().into_boxed_slice(),
))
}
not_found => Err(ProtocolConversionError::NotFound(not_found)),
}
}

View file

@ -289,6 +289,17 @@ impl From<crate::normal::ForceUpdateInfo> for ForceUpdateInfo {
}
}
}
impl From<crate::normal::ClientLoadingCostumeVerificationNotify>
for ClientLoadingCostumeVerificationNotify {
fn from(value: crate::normal::ClientLoadingCostumeVerificationNotify) -> Self {
Self {
costume_id: value.costume_id.into(),
prefab_hash: value.prefab_hash.into(),
guid: value.guid.into(),
..Default::default()
}
}
}
impl From<crate::normal::AvatarTeam> for AvatarTeam {
fn from(value: crate::normal::AvatarTeam) -> Self {
Self {
@ -419,6 +430,11 @@ impl From<crate::normal::AvatarDataNotify> for AvatarDataNotify {
.into_iter()
.map(|v| v.into())
.collect(),
owned_costume_list: value
.owned_costume_list
.into_iter()
.map(|v| v.into())
.collect(),
choose_avatar_guid: value.choose_avatar_guid.into(),
avatar_team_map: value
.avatar_team_map
@ -1250,6 +1266,14 @@ impl From<crate::normal::GadgetPlayInfo> for GadgetPlayInfo {
}
}
}
impl From<crate::normal::AvatarChangeCostumeNotify> for AvatarChangeCostumeNotify {
fn from(value: crate::normal::AvatarChangeCostumeNotify) -> Self {
Self {
entity_info: value.entity_info.map(|v| v.into()),
..Default::default()
}
}
}
impl From<crate::normal::Material> for Material {
fn from(value: crate::normal::Material) -> Self {
Self {
@ -1567,6 +1591,16 @@ impl From<crate::normal::SceneForceUnlockNotify> for SceneForceUnlockNotify {
}
}
}
impl From<crate::normal::HomeAvatarCostumeChangeNotify>
for HomeAvatarCostumeChangeNotify {
fn from(value: crate::normal::HomeAvatarCostumeChangeNotify) -> Self {
Self {
costume_id: value.costume_id.into(),
avatar_id: value.avatar_id.into(),
..Default::default()
}
}
}
impl From<crate::normal::AiSyncInfo> for AiSyncInfo {
fn from(value: crate::normal::AiSyncInfo) -> Self {
Self {
@ -2410,6 +2444,15 @@ impl From<crate::normal::MassivePropSyncInfo> for MassivePropSyncInfo {
}
}
}
impl From<crate::normal::AvatarChangeCostumeReq> for AvatarChangeCostumeReq {
fn from(value: crate::normal::AvatarChangeCostumeReq) -> Self {
Self {
costume_id: value.costume_id.into(),
avatar_guid: value.avatar_guid.into(),
..Default::default()
}
}
}
impl From<crate::normal::BreakoutVector2> for BreakoutVector2 {
fn from(value: crate::normal::BreakoutVector2) -> Self {
Self {
@ -2649,3 +2692,13 @@ impl From<crate::normal::SetUpAvatarTeamRsp> for SetUpAvatarTeamRsp {
}
}
}
impl From<crate::normal::AvatarChangeCostumeRsp> for AvatarChangeCostumeRsp {
fn from(value: crate::normal::AvatarChangeCostumeRsp) -> Self {
Self {
costume_id: value.costume_id.into(),
avatar_guid: value.avatar_guid.into(),
retcode: value.retcode.into(),
..Default::default()
}
}
}

View file

@ -94,7 +94,7 @@ pub fn player_join_team(
},
appearance: AvatarAppearance {
flycloak_id: to_spawn.wearing_flycloak_id,
costume_id: 0, // TODO!
costume_id: to_spawn.costume_id,
},
transform: Transform {
position: player_info.world_position.position.into(),