478 lines
17 KiB
Rust
478 lines
17 KiB
Rust
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
|
|
pub use in_world_player::InWorldPlayer;
|
|
use wicked_waifus_protocol_internal::{PlayerBasicData, PlayerRoleData, PlayerSaveData};
|
|
use wicked_waifus_protocol::{AdventreTask, AdventureManualData, AdventureUpdateNotify, AdviceSettingNotify, ControlInfoNotify, EEntityType, EntityAddNotify, EntityConfigType, EntityPb, EntityRemoveInfo, EntityRemoveNotify, EntityState, ERemoveEntityType, FightFormationNotifyInfo, FightRoleInfo, FightRoleInfos, FormationRoleInfo, GroupFormation, InstDataNotify, ItemPkgOpenNotify, LivingStatus, MapUnlockFieldNotify, PbGetRoleListNotify, PlayerFightFormations, ProtocolUnit, RoleChangeUnlockNotify, UpdateFormationNotify, UpdateGroupFormationNotify};
|
|
use wicked_waifus_protocol::message::Message;
|
|
use wicked_waifus_commons::time_util;
|
|
use wicked_waifus_data::base_property_data;
|
|
use wicked_waifus_data::role_info_data;
|
|
|
|
use crate::{create_player_entity_pb, query_components};
|
|
use crate::logic::{
|
|
components::{
|
|
Attribute, EntityConfig, Equip, FightBuff, Movement, OwnerPlayer, PlayerEntityMarker,
|
|
Position, Visibility, VisionSkill,
|
|
},
|
|
ecs::component::ComponentContainer,
|
|
};
|
|
use crate::logic::components::RoleSkin;
|
|
use crate::logic::ecs::world::WorldEntity;
|
|
use crate::logic::player::basic_info::PlayerBasicInfo;
|
|
use crate::logic::player::explore_tools::ExploreTools;
|
|
use crate::logic::player::location::PlayerLocation;
|
|
use crate::logic::player::player_chat::PlayerChat;
|
|
use crate::logic::player::player_func::PlayerFunc;
|
|
use crate::logic::player::guides::Guides;
|
|
use crate::session::Session;
|
|
|
|
use super::{
|
|
ecs::world::World,
|
|
role::{Role, RoleFormation},
|
|
};
|
|
|
|
mod basic_info;
|
|
mod explore_tools;
|
|
mod in_world_player;
|
|
mod location;
|
|
mod player_func;
|
|
mod player_chat;
|
|
mod guides;
|
|
|
|
pub struct Player {
|
|
session: Option<Arc<Session>>,
|
|
// Persistent
|
|
pub basic_info: PlayerBasicInfo,
|
|
pub role_list: HashMap<i32, Role>,
|
|
pub formation_list: HashMap<i32, RoleFormation>,
|
|
pub cur_formation_id: i32,
|
|
pub location: PlayerLocation,
|
|
pub func: PlayerFunc,
|
|
pub explore_tools: ExploreTools,
|
|
pub player_chat: PlayerChat,
|
|
pub guides: Guides,
|
|
// Runtime
|
|
pub world: Rc<RefCell<World>>,
|
|
pub last_save_time: u64,
|
|
pub quadrant_id: u64,
|
|
}
|
|
|
|
impl Player {
|
|
pub fn init(&mut self) {
|
|
if self.role_list.is_empty() || self.formation_list.is_empty() {
|
|
self.init_role_and_formation();
|
|
}
|
|
|
|
self.ensure_basic_unlock_func();
|
|
}
|
|
|
|
pub fn notify_general_data(&self) {
|
|
self.notify(self.build_adventure_task_notify());
|
|
self.notify(AdviceSettingNotify {
|
|
is_show: true,
|
|
});
|
|
self.notify(self.basic_info.build_notify());
|
|
self.notify(ControlInfoNotify {
|
|
forbid_list: vec![], // Disable function prohibition
|
|
});
|
|
// CalabashMsgNotify + CalabashLevelRewardsNotify
|
|
self.notify(self.func.build_func_open_notify());
|
|
// RoleFavorListNotify
|
|
self.notify(InstDataNotify {
|
|
enter_infos: vec![], // TODO: No effect in normal world, to implement for dungeon::logic()
|
|
});
|
|
self.notify(ItemPkgOpenNotify {
|
|
open_pkg: (0..8).collect(),
|
|
});
|
|
// TODO: [WWPS-1] Real implementation should fetch completed / uncompleted from db, lets return completed
|
|
for i in 0..40 {
|
|
self.notify(MapUnlockFieldNotify {
|
|
field_id: i,
|
|
});
|
|
}
|
|
self.notify(self.build_role_list_notify());
|
|
self.notify(self.explore_tools.build_explore_tool_all_notify());
|
|
self.notify(self.explore_tools.build_roulette_update_notify());
|
|
// VisionExploreSkillNotify
|
|
self.notify(RoleChangeUnlockNotify {
|
|
unlock_role_ids: vec![
|
|
Role::MAIN_CHARACTER_FEMALE_SPECTRO_ID,
|
|
Role::MAIN_CHARACTER_MALE_SPECTRO_ID,
|
|
Role::MAIN_CHARACTER_MALE_HAVOC_ID,
|
|
Role::MAIN_CHARACTER_FEMALE_HAVOC_ID,
|
|
],
|
|
next_allow_change_time: 0,
|
|
});
|
|
// EnergyUpdateNotify
|
|
// LevelPlayInfoNotify
|
|
// MailInfoNotify
|
|
// PayShopInfoNotify
|
|
// PlayerVarNotify
|
|
// RoguelikeCurrencyNotify
|
|
// RoleMotionListNotify
|
|
// SettingNotify
|
|
}
|
|
|
|
fn init_role_and_formation(&mut self) {
|
|
self.role_list.clear();
|
|
let mut role = match self.basic_info.sex {
|
|
0 => Role::new(Role::MAIN_CHARACTER_FEMALE_SPECTRO_ID),
|
|
1 => Role::new(Role::MAIN_CHARACTER_MALE_SPECTRO_ID),
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
role.name = self.basic_info.name.clone();
|
|
|
|
self.role_list.insert(role.role_id, role);
|
|
|
|
let required_role_ids: Vec<i32> = role_info_data::iter()
|
|
.filter(|role_info| role_info.role_type == 1)
|
|
.map(|role_info| role_info.id)
|
|
.collect();
|
|
let formation = vec![1506, 1206, 1505];
|
|
|
|
required_role_ids.iter().for_each(|&role_id| {
|
|
if !self.role_list.keys().any(|&k| k == role_id) {
|
|
self.role_list.insert(role_id, Role::new(role_id));
|
|
}
|
|
});
|
|
|
|
self.formation_list.insert(
|
|
1,
|
|
RoleFormation {
|
|
id: 1,
|
|
cur_role: *formation.iter().next().unwrap(),
|
|
role_ids: formation,
|
|
is_current: true,
|
|
},
|
|
);
|
|
self.cur_formation_id = 1;
|
|
|
|
self.formation_list.values_mut().for_each(|formation| {
|
|
if formation.is_current && formation.id != 1 {
|
|
formation.is_current = false;
|
|
}
|
|
});
|
|
|
|
self.ensure_current_formation();
|
|
}
|
|
|
|
// Ensure basic functionality is unlocked
|
|
// Should be handled by quest progression,
|
|
// but as of right now, just unlock what we need
|
|
fn ensure_basic_unlock_func(&mut self) {
|
|
self.func.unlock(10026); // explore tools
|
|
}
|
|
|
|
fn ensure_current_formation(&mut self) {
|
|
// If the list off formation is empty, add a default formation
|
|
if self.formation_list.is_empty() {
|
|
let mut role_list_clone = self.role_list.iter().clone();
|
|
|
|
self.formation_list.insert(
|
|
1,
|
|
RoleFormation {
|
|
id: 1,
|
|
cur_role: role_list_clone.next().unwrap().1.role_id,
|
|
role_ids: role_list_clone
|
|
.take(3)
|
|
.map(|(&role_id, _)| role_id)
|
|
.collect(),
|
|
is_current: true,
|
|
},
|
|
);
|
|
}
|
|
|
|
// If there is no current formation, set the first formation as the current formation
|
|
if !self.formation_list.values().any(|rf| rf.is_current) {
|
|
self.formation_list.get_mut(&1).unwrap().is_current = true;
|
|
}
|
|
|
|
// Ensure that the set of character IDs for the current formation is not empty and that the current character ID is in the set
|
|
if let Some(rf) = self.formation_list.values_mut().find(|rf| rf.is_current) {
|
|
if rf.role_ids.is_empty() {
|
|
rf.role_ids
|
|
.push(self.role_list.iter().next().unwrap().1.role_id);
|
|
}
|
|
|
|
if !rf.role_ids.contains(&rf.cur_role) {
|
|
rf.cur_role = *rf.role_ids.iter().nth(0).unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn build_in_world_player(&self) -> InWorldPlayer {
|
|
InWorldPlayer {
|
|
player_id: self.basic_info.id,
|
|
player_name: self.basic_info.name.clone(),
|
|
player_icon: 0,
|
|
level: self.basic_info.level,
|
|
group_type: 1,
|
|
}
|
|
}
|
|
|
|
pub fn build_adventure_task_notify(&self) -> AdventureUpdateNotify {
|
|
// TODO: [WWPS-1] Real implementation should fetch completed / uncompleted from db, lets return completed
|
|
let adventure_task_data = wicked_waifus_data::adventure_task_data::iter();
|
|
|
|
AdventureUpdateNotify {
|
|
adventure_manual_data: vec![
|
|
AdventureManualData {
|
|
adventre_task: adventure_task_data
|
|
.map(|task| AdventreTask {
|
|
id:task.id,
|
|
adventre_progress: 1,
|
|
state:2,
|
|
})
|
|
.collect(),
|
|
now_chapter:9,
|
|
received_chapter:9,
|
|
}
|
|
],
|
|
}
|
|
}
|
|
|
|
pub fn build_player_entity_add_notify(
|
|
&self,
|
|
role_list: Vec<Role>,
|
|
world: &mut WorldEntity,
|
|
) -> EntityAddNotify {
|
|
create_player_entity_pb!(
|
|
role_list,
|
|
self.basic_info.cur_map_id,
|
|
world,
|
|
self.basic_info.id,
|
|
self.location.position.clone(),
|
|
self.explore_tools
|
|
)
|
|
}
|
|
|
|
pub fn build_player_entity_remove_notify(
|
|
&self,
|
|
entities: Vec<i64>,
|
|
remove_type: ERemoveEntityType,
|
|
) -> EntityRemoveNotify {
|
|
EntityRemoveNotify {
|
|
remove_infos: entities
|
|
.iter()
|
|
.map(|&entity_id| EntityRemoveInfo {
|
|
entity_id,
|
|
r#type: remove_type.into(),
|
|
})
|
|
.collect(),
|
|
is_remove: true,
|
|
}
|
|
}
|
|
|
|
pub fn build_update_group_formation_notify(
|
|
&self,
|
|
cur_formation: RoleFormation,
|
|
world: &mut WorldEntity,
|
|
) -> UpdateGroupFormationNotify {
|
|
let group_type = 1;
|
|
UpdateGroupFormationNotify {
|
|
group_formation: vec![GroupFormation {
|
|
player_id: self.basic_info.id,
|
|
fight_role_infos: vec![FightRoleInfos {
|
|
group_type,
|
|
fight_role_infos: cur_formation
|
|
.role_ids
|
|
.iter()
|
|
.map(|&role_id| {
|
|
let entity_id = world.get_entity_id(role_id);
|
|
let role_skin = query_components!(world, entity_id, RoleSkin).0.unwrap();
|
|
FightRoleInfo {
|
|
role_id,
|
|
entity_id: world.get_entity_id(role_id),
|
|
on_stage_without_control: false,
|
|
role_skin_id: role_skin.skin_id,
|
|
}
|
|
})
|
|
.collect(),
|
|
cur_role: cur_formation.cur_role,
|
|
// is_retain: false,
|
|
living_status: LivingStatus::Alive.into(),
|
|
is_fixed_location: false,
|
|
}],
|
|
current_group_type: group_type,
|
|
}],
|
|
}
|
|
}
|
|
|
|
pub fn build_update_formation_notify(&self) -> UpdateFormationNotify {
|
|
let role_map: HashMap<i32, &Role> = self
|
|
.role_list
|
|
.values()
|
|
.map(|role| (role.role_id, role))
|
|
.collect();
|
|
|
|
UpdateFormationNotify {
|
|
players_formations: vec![PlayerFightFormations {
|
|
player_id: self.basic_info.id,
|
|
formations: self
|
|
.formation_list
|
|
.iter()
|
|
.map(|(&formation_id, formation)| FightFormationNotifyInfo {
|
|
formation_id,
|
|
cur_role: formation.cur_role,
|
|
role_infos: formation
|
|
.role_ids
|
|
.iter()
|
|
.map(|role_id| {
|
|
if !role_map.contains_key(role_id) {
|
|
tracing::warn!("Role {} not found in use role list", role_id);
|
|
return Default::default();
|
|
}
|
|
let role = *role_map.get(&role_id).unwrap();
|
|
FormationRoleInfo {
|
|
role_id: role.role_id,
|
|
max_hp: 0,
|
|
cur_hp: 0,
|
|
level: role.level,
|
|
role_skin_id: role.skin_id,
|
|
}
|
|
})
|
|
.collect(),
|
|
is_current: formation.is_current,
|
|
})
|
|
.collect(),
|
|
}],
|
|
}
|
|
}
|
|
|
|
pub fn load_from_save(save_data: PlayerSaveData) -> Self {
|
|
let role_data = save_data.role_data.unwrap_or_default();
|
|
|
|
Self {
|
|
session: None,
|
|
basic_info: PlayerBasicInfo::load_from_save(
|
|
save_data.basic_data.clone().unwrap_or_default(),
|
|
),
|
|
role_list: role_data
|
|
.role_list
|
|
.into_iter()
|
|
.map(Role::load_from_save)
|
|
.collect::<HashMap<i32, Role>>(),
|
|
formation_list: role_data
|
|
.role_formation_list
|
|
.into_iter()
|
|
.map(|(k, v)| (k, RoleFormation::load_from_save(v)))
|
|
.collect(),
|
|
cur_formation_id: role_data.cur_formation_id,
|
|
location: save_data
|
|
.location_data
|
|
.map(PlayerLocation::load_from_save)
|
|
.unwrap_or_default(),
|
|
func: save_data
|
|
.func_data
|
|
.map(PlayerFunc::load_from_save)
|
|
.unwrap_or_default(),
|
|
explore_tools: save_data
|
|
.explore_tools_data
|
|
.map(ExploreTools::load_from_save)
|
|
.unwrap_or_default(),
|
|
player_chat: save_data
|
|
.chat_data
|
|
.map(PlayerChat::load_from_save)
|
|
.unwrap_or_default(),
|
|
guides: save_data
|
|
.guides
|
|
.map(Guides::load_from_save)
|
|
.unwrap_or_default(),
|
|
world: Rc::new(RefCell::new(World::new())),
|
|
last_save_time: time_util::unix_timestamp(),
|
|
quadrant_id: 0,
|
|
}
|
|
}
|
|
|
|
pub fn build_save_data(&self) -> PlayerSaveData {
|
|
PlayerSaveData {
|
|
basic_data: Some(self.basic_info.build_save_data()),
|
|
role_data: Some(PlayerRoleData {
|
|
role_list: self
|
|
.role_list
|
|
.iter()
|
|
.map(|(_, role)| role.build_save_data())
|
|
.collect(),
|
|
role_formation_list: self
|
|
.formation_list
|
|
.iter()
|
|
.map(|(&k, v)| (k, v.build_save_data()))
|
|
.collect(),
|
|
cur_formation_id: self.cur_formation_id,
|
|
}),
|
|
location_data: Some(self.location.build_save_data()),
|
|
func_data: Some(self.func.build_save_data()),
|
|
explore_tools_data: Some(self.explore_tools.build_save_data()),
|
|
chat_data: Some(self.player_chat.build_save_data()),
|
|
guides: Some(self.guides.build_save_data()),
|
|
}
|
|
}
|
|
|
|
pub fn set_session(&mut self, session: Arc<Session>) {
|
|
self.session = Some(session);
|
|
}
|
|
|
|
pub fn build_role_list_notify(&self) -> PbGetRoleListNotify {
|
|
PbGetRoleListNotify {
|
|
role_list: self
|
|
.role_list
|
|
.iter()
|
|
.map(|(_, role)| role.to_protobuf())
|
|
.take(3) // TODO: There is a bug we are investigating with several resonators, this is a workaround
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
pub fn notify(&self, content: impl ProtocolUnit) {
|
|
if let Some(session) = self.session.as_ref() {
|
|
session.forward_to_gateway(Message::Push {
|
|
sequence_number: 0,
|
|
message_id: content.get_message_id(),
|
|
payload: Some(content.encode_to_vec().into_boxed_slice()),
|
|
});
|
|
}
|
|
}
|
|
|
|
pub fn respond(&self, content: impl ProtocolUnit, rpc_id: u16) {
|
|
if let Some(session) = self.session.as_ref() {
|
|
let data = content.encode_to_vec().into_boxed_slice();
|
|
tracing::debug!("Push to gateway MSG_ID: {}, LEN: {}, DATA: {}", content.get_message_id(), data.len(), hex::encode(&data));
|
|
let response = Message::Response {
|
|
sequence_number: 0,
|
|
message_id: content.get_message_id(),
|
|
rpc_id,
|
|
payload: Some(data),
|
|
};
|
|
// let (name, value) = ("PbGetRoleListNotify", serde_json::to_string_pretty(&list).unwrap());
|
|
// tracing::debug!("trying to log unhandled data for message {name} with:\n{value}");
|
|
session.forward_to_gateway(response);
|
|
}
|
|
}
|
|
|
|
pub fn create_default_save_data(id: i32, name: String, sex: i32) -> PlayerSaveData {
|
|
let role_id = match sex {
|
|
0 => Role::MAIN_CHARACTER_FEMALE_SPECTRO_ID, // 1502
|
|
1 => Role::MAIN_CHARACTER_MALE_SPECTRO_ID, // 1501
|
|
_ => Role::MAIN_CHARACTER_MALE_SPECTRO_ID, // Default to male
|
|
};
|
|
|
|
PlayerSaveData {
|
|
basic_data: Some(PlayerBasicData {
|
|
id,
|
|
name,
|
|
sex,
|
|
level: 1,
|
|
head_photo: 1603,
|
|
head_frame: 80060009,
|
|
cur_map_id: 8,
|
|
role_show_list: vec![role_id],
|
|
..Default::default()
|
|
}),
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|