Implement weapons

Implement Weapon add, save, equip
Implement 'item add_weapon' command
This commit is contained in:
xeon 2024-07-24 21:36:02 +03:00
parent 370f141f88
commit 69634fda64
16 changed files with 2152 additions and 25 deletions

File diff suppressed because it is too large Load diff

View file

@ -43,4 +43,5 @@ template_tables! {
ProcedureConfigTemplate;
PostGirlConfigTemplate;
TrainingQuestTemplate;
WeaponTemplate;
}

View file

@ -0,0 +1,34 @@
use serde::Deserialize;
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct WeaponTemplate {
#[serde(rename = "ItemID")]
pub item_id: u32,
pub code_name: String,
#[serde(rename = "Type")]
pub weapon_type: u32,
pub base_property: IntPropertyIntValue,
pub rand_property: IntPropertyIntValue,
pub star_limit: u32,
pub exp_recycle: u32,
pub refine_costs: Vec<IntItemIDIntNumber>,
pub refine_initial: u32,
pub refine_limit: u32,
}
// nice names, mihoyo
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct IntPropertyIntValue {
pub property: u32,
pub value: i32,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct IntItemIDIntNumber {
#[serde(rename = "ItemID")]
pub item_id: u32,
pub number: u32,
}

View file

@ -3,7 +3,12 @@ use proto::{AddAvatarPerformType, AddAvatarScNotify, PlayerSyncScNotify};
use crate::ServerState;
pub async fn add(args: &[&str], state: &ServerState) -> Result<String, Box<dyn std::error::Error>> {
use super::ArgSlice;
pub async fn add(
args: ArgSlice<'_>,
state: &ServerState,
) -> Result<String, Box<dyn std::error::Error>> {
const USAGE: &str = "Usage: avatar add [player_uid] [avatar_id]";
if args.len() != 2 {

View file

@ -0,0 +1,52 @@
use data::tables;
use proto::PlayerSyncScNotify;
use crate::ServerState;
use super::ArgSlice;
pub async fn add_weapon(
args: ArgSlice<'_>,
state: &ServerState,
) -> Result<String, Box<dyn std::error::Error>> {
const USAGE: &str = "Usage: item add_weapon [player_uid] [weapon_id]";
if args.len() != 2 {
return Ok(USAGE.to_string());
}
let uid = args[0].parse::<u32>()?;
let weapon_id = args[1].parse::<u32>()?;
if !tables::weapon_template_tb::iter().any(|tmpl| tmpl.item_id == weapon_id) {
return Ok(format!("weapon with id {weapon_id} doesn't exist"));
}
let Some(player_lock) = state.player_mgr.get_player(uid).await else {
return Ok(String::from("player not found"));
};
let (session_id, weapon_uid, item_sync) = {
let mut player = player_lock.lock().await;
let uid = player.item_model.add_weapon(weapon_id);
(
player.current_session_id(),
uid.value(),
player.item_model.item_sync(),
)
};
if let Some(session) = session_id.map(|id| state.session_mgr.get(id)).flatten() {
session
.notify(PlayerSyncScNotify {
item_sync: Some(item_sync),
..Default::default()
})
.await?;
} else {
state.player_mgr.save_and_remove(uid).await;
}
Ok(format!("successfully added weapon, item uid: {weapon_uid}"))
}

View file

@ -6,8 +6,11 @@ use rustyline_async::{Readline, ReadlineEvent, SharedWriter};
use crate::ServerState;
mod avatar;
mod item;
mod player;
type ArgSlice<'a> = &'a [&'a str];
pub struct CommandManager {
state: Arc<ServerState>,
}
@ -83,5 +86,6 @@ impl CommandManager {
player::nickname "[player_uid] [nickname]" "changes player nickname";
player::procedure "[player_uid] [procedure_id]" "changes current beginner procedure id, parameter -1 can be used for skipping it";
avatar::add "[player_uid] [avatar_id]" "gives avatar with specified id to player";
item::add_weapon "[player_uid] [weapon_id]" "gives weapon with specified id to player";
}
}

View file

@ -3,8 +3,10 @@ use proto::PlayerSyncScNotify;
use crate::ServerState;
use super::ArgSlice;
pub async fn avatar(
args: &[&str],
args: ArgSlice<'_>,
state: &ServerState,
) -> Result<String, Box<dyn std::error::Error>> {
const USAGE: &str = "Usage: player avatar [player_uid] [avatar_id]";
@ -40,7 +42,7 @@ pub async fn avatar(
}
pub async fn nickname(
args: &[&str],
args: ArgSlice<'_>,
state: &ServerState,
) -> Result<String, Box<dyn std::error::Error>> {
const USAGE: &str = "Usage: player nickname [uid] [nickname]";
@ -89,7 +91,7 @@ pub async fn nickname(
}
pub async fn procedure(
args: &[&str],
args: ArgSlice<'_>,
state: &ServerState,
) -> Result<String, Box<dyn std::error::Error>> {
const USAGE: &str = "Usage: player procedure [uid] [procedure_id]";

View file

@ -10,6 +10,53 @@ pub async fn on_get_item_data(
Ok(GetItemDataScRsp {
retcode: Retcode::RetSucc.into(),
resource_list: item_model.resources.iter().map(|i| i.to_client()).collect(),
weapon_list: item_model.weapons.iter().map(|w| w.to_client()).collect(),
..Default::default()
})
}
pub async fn on_weapon_dress(
session: &NetSession,
player: &mut Player,
req: WeaponDressCsReq,
) -> NetResult<WeaponDressScRsp> {
player.dress_weapon(req.avatar_id, req.weapon_uid.into())?;
session
.notify(PlayerSyncScNotify {
avatar: Some(player.role_model.avatar_sync()),
item_sync: Some(player.item_model.item_sync()),
..Default::default()
})
.await?;
Ok(WeaponDressScRsp {
retcode: Retcode::RetSucc.into(),
})
}
pub async fn on_weapon_un_dress(
session: &NetSession,
player: &mut Player,
req: WeaponUnDressCsReq,
) -> NetResult<WeaponUnDressScRsp> {
let avatar = player
.role_model
.avatar_list
.iter_mut()
.find(|a| a.template_id == req.avatar_id)
.ok_or(Retcode::RetFail)?;
avatar.weapon_uid = None;
session
.notify(PlayerSyncScNotify {
avatar: Some(player.role_model.avatar_sync()),
..Default::default()
})
.await?;
Ok(WeaponUnDressScRsp {
retcode: Retcode::RetSucc.into(),
})
}

View file

@ -109,6 +109,8 @@ req_handlers! {
misc::GetServerTimestamp;
misc::GetCutSceneKeyInfo;
embattles::ReportBattleTeam;
item::WeaponDress;
item::WeaponUnDress;
}
notify_handlers! {

View file

@ -1,24 +1,30 @@
use super::ResourceItem;
use proto::ItemModelBin;
use super::{ItemUID, ResourceItem, Weapon};
use proto::{ItemModelBin, ItemSync};
pub struct ItemModel {
uid_counter: u32,
pub resources: Vec<ResourceItem>,
pub weapons: Vec<Weapon>,
}
impl ItemModel {
pub fn from_bin(bin: ItemModelBin) -> Self {
Self {
uid_counter: bin.item_uid_counter,
resources: bin
.resource_list
.into_iter()
.map(ResourceItem::from_bin)
.collect(),
weapons: bin.weapon_list.into_iter().map(Weapon::from_bin).collect(),
}
}
pub fn to_bin(&self) -> ItemModelBin {
ItemModelBin {
item_uid_counter: self.uid_counter,
resource_list: self.resources.iter().map(ResourceItem::to_bin).collect(),
weapon_list: self.weapons.iter().map(Weapon::to_bin).collect(),
}
}
@ -36,12 +42,34 @@ impl ItemModel {
});
}
}
pub fn add_weapon(&mut self, template_id: u32) -> ItemUID {
let uid = self.next_uid();
self.weapons.push(Weapon::new(template_id, uid));
uid
}
pub fn item_sync(&self) -> ItemSync {
ItemSync {
resource_list: self.resources.iter().map(|i| i.to_client()).collect(),
weapon_list: self.weapons.iter().map(|w| w.to_client()).collect(),
..Default::default()
}
}
fn next_uid(&mut self) -> ItemUID {
self.uid_counter += 1;
self.uid_counter.into()
}
}
impl Default for ItemModel {
fn default() -> Self {
Self {
uid_counter: 0,
resources: Vec::new(),
weapons: Vec::new(),
}
}
}

View file

@ -1,5 +1,22 @@
mod item_model;
mod resource;
mod weapon;
pub use item_model::ItemModel;
pub use resource::ResourceItem;
pub use weapon::Weapon;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ItemUID(u32);
impl ItemUID {
pub fn value(&self) -> u32 {
self.0
}
}
impl From<u32> for ItemUID {
fn from(value: u32) -> Self {
Self(value)
}
}

View file

@ -0,0 +1,66 @@
use super::ItemUID;
use data::tables::{self, WeaponTemplate};
use proto::{WeaponBin, WeaponInfo};
pub struct Weapon {
pub uid: ItemUID,
pub template_id: u32,
pub star: u32,
pub level: u32,
pub exp: u32,
pub refine_level: u32,
}
impl Weapon {
pub fn new(template_id: u32, uid: ItemUID) -> Self {
let template = Self::get_template(template_id).unwrap();
Self {
template_id,
uid,
level: (template.star_limit + 1) * 10,
star: template.star_limit,
refine_level: template.refine_limit,
exp: 0,
}
}
pub fn from_bin(bin: WeaponBin) -> Self {
Self {
template_id: bin.template_id,
uid: bin.uid.into(),
star: bin.star,
level: bin.level,
exp: bin.exp,
refine_level: bin.refine_level,
}
}
pub fn to_bin(&self) -> WeaponBin {
WeaponBin {
template_id: self.template_id,
uid: self.uid.value(),
star: self.star,
level: self.level,
exp: self.exp,
refine_level: self.refine_level,
}
}
pub fn to_client(&self) -> WeaponInfo {
WeaponInfo {
template_id: self.template_id,
uid: self.uid.value(),
star: self.star,
level: self.level,
exp: self.exp,
refine_level: self.refine_level,
..Default::default()
}
}
fn get_template(template_id: u32) -> Option<&'static WeaponTemplate> {
tables::weapon_template_tb::iter().find(|tmpl| tmpl.item_id == template_id)
}
}

View file

@ -2,7 +2,7 @@ use data::tables;
use proto::{ItemStatic, PlayerDataBin, Retcode};
use super::game::{FrontendGame, GameInstance, LogicError};
use super::item::ItemModel;
use super::item::{ItemModel, ItemUID};
use super::main_city_model::MainCityModel;
use super::role::RoleModel;
use super::{BasicDataModel, LockModel};
@ -90,6 +90,31 @@ impl Player {
Ok(())
}
pub fn dress_weapon(&mut self, avatar_id: u32, weapon_uid: ItemUID) -> Result<(), Retcode> {
self.item_model
.weapons
.iter()
.any(|w| w.uid == weapon_uid)
.then_some(())
.ok_or(Retcode::RetFail)?;
self.role_model
.avatar_list
.iter_mut()
.filter(|a| a.weapon_uid.map(|u| u.value()).unwrap_or_default() == weapon_uid.value())
.for_each(|a| a.weapon_uid = None);
let avatar = self
.role_model
.avatar_list
.iter_mut()
.find(|a| a.template_id == avatar_id)
.ok_or(Retcode::RetFail)?;
avatar.weapon_uid = Some(weapon_uid);
Ok(())
}
pub fn set_current_session(&mut self, id: u64) {
self.current_session_id = Some(id);
}

View file

@ -1,5 +1,7 @@
use proto::{AvatarBin, AvatarInfo, AvatarSkillInfo};
use crate::logic::item::ItemUID;
use super::AvatarSkill;
pub const AVATAR_TALENT_COUNT: usize = 6;
@ -10,6 +12,7 @@ pub struct Avatar {
pub star: u32,
pub rank: u32,
pub unlocked_talent_num: u32,
pub weapon_uid: Option<ItemUID>,
pub skill_list: Vec<AvatarSkill>,
pub talent_switch: [bool; AVATAR_TALENT_COUNT],
}
@ -23,6 +26,7 @@ impl Avatar {
star: 0,
rank: 6,
unlocked_talent_num: 6,
weapon_uid: None,
skill_list: (0..=6)
.map(|st| AvatarSkill {
skill_type: st,
@ -41,6 +45,7 @@ impl Avatar {
star: bin.star,
rank: bin.rank,
unlocked_talent_num: bin.unlocked_talent_num,
weapon_uid: (bin.weapon_uid != 0).then_some(bin.weapon_uid.into()),
skill_list: bin
.avatar_skill_list
.into_iter()
@ -61,6 +66,7 @@ impl Avatar {
star: self.star,
rank: self.rank,
unlocked_talent_num: self.unlocked_talent_num,
weapon_uid: self.weapon_uid.map(|u| u.value()).unwrap_or_default(),
avatar_skill_list: self.skill_list.iter().map(AvatarSkill::to_bin).collect(),
talent_switch_list: self.talent_switch.to_vec(),
}
@ -82,6 +88,7 @@ impl Avatar {
rank: self.rank,
talent_switch_list: self.talent_switch.to_vec(),
unlocked_talent_num: self.unlocked_talent_num,
cur_weapon_uid: self.weapon_uid.map(|u| u.value()).unwrap_or_default(),
..Default::default()
}
}

View file

@ -2264,7 +2264,7 @@ pub struct PlayerSyncScNotify {
#[prost(message, optional, tag = "9")]
pub oalcbialjcp: ::core::option::Option<Lokebkejkad>,
#[prost(message, optional, tag = "10")]
pub pofnfakeaif: ::core::option::Option<Bleckcnpkej>,
pub item_sync: ::core::option::Option<ItemSync>,
#[prost(message, optional, tag = "11")]
pub ncfafdpojjh: ::core::option::Option<Efkhdkkganm>,
#[prost(message, optional, tag = "12")]
@ -7521,7 +7521,7 @@ pub struct Mjcibhpoogl {}
#[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Mdilomeanog {
pub struct WeaponInfo {
#[xor(8461)]
#[prost(uint32, tag = "1")]
pub star: u32,
@ -7541,7 +7541,7 @@ pub struct Mdilomeanog {
pub uid: u32,
#[xor(14552)]
#[prost(uint32, tag = "9")]
pub imedhbicngp: u32,
pub refine_level: u32,
#[prost(bool, tag = "6")]
pub omockkcjpgk: bool,
}
@ -11186,7 +11186,7 @@ pub struct Ndpkppjmine {
#[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Iakibnbffhf {
pub struct WeaponUnDressCsReq {
#[xor(1124)]
#[prost(uint32, tag = "11")]
pub avatar_id: u32,
@ -13724,7 +13724,7 @@ pub struct Omdfkjoopce {
pub amhnddiknek: u32,
#[xor(3813)]
#[prost(uint32, tag = "14")]
pub ekdhfcplchk: u32,
pub weapon_uid: u32,
}
#[derive(proto_gen::CmdID)]
#[cmdid(1021)]
@ -14032,7 +14032,7 @@ pub struct Nffblkigaan {
pub mpojmpolhlp: ::prost::alloc::vec::Vec<u32>,
#[xor(2070)]
#[prost(uint32, tag = "11")]
pub ekdhfcplchk: u32,
pub weapon_uid: u32,
#[xor(2492)]
#[prost(uint32, tag = "9")]
pub eijhjbplhih: u32,
@ -15826,7 +15826,7 @@ pub struct Ekmmggfgijn {
#[prost(message, repeated, tag = "13")]
pub avatar_list: ::prost::alloc::vec::Vec<AvatarInfo>,
#[prost(message, repeated, tag = "14")]
pub gpdcidjenda: ::prost::alloc::vec::Vec<Mdilomeanog>,
pub weapon_list: ::prost::alloc::vec::Vec<WeaponInfo>,
}
#[derive(proto_gen::CmdID)]
#[derive(proto_gen::XorFields)]
@ -16482,7 +16482,7 @@ pub struct DungeonItemData {
#[prost(uint32, tag = "5")]
pub gebpmceiane: u32,
#[prost(message, repeated, tag = "13")]
pub gpdcidjenda: ::prost::alloc::vec::Vec<Mdilomeanog>,
pub weapon_list: ::prost::alloc::vec::Vec<WeaponInfo>,
#[prost(message, repeated, tag = "11")]
pub gonbpdlffco: ::prost::alloc::vec::Vec<Ggabkdpcncc>,
#[prost(message, repeated, tag = "6")]
@ -18485,7 +18485,7 @@ pub struct Mdpcbogenpf {
#[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Bleckcnpkej {
pub struct ItemSync {
#[prost(message, optional, tag = "1")]
pub jgifpnjbbcp: ::core::option::Option<Nkpkchipibi>,
#[prost(uint32, repeated, tag = "2")]
@ -18493,7 +18493,7 @@ pub struct Bleckcnpkej {
#[prost(uint32, repeated, tag = "5")]
pub dffmeljklii: ::prost::alloc::vec::Vec<u32>,
#[prost(message, repeated, tag = "6")]
pub gpdcidjenda: ::prost::alloc::vec::Vec<Mdilomeanog>,
pub weapon_list: ::prost::alloc::vec::Vec<WeaponInfo>,
#[prost(message, repeated, tag = "7")]
pub resource_list: ::prost::alloc::vec::Vec<Resource>,
#[prost(uint32, repeated, tag = "8")]
@ -21415,10 +21415,10 @@ pub struct Bfdmichhlen {}
#[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Ckgocemenbc {
pub struct WeaponDressCsReq {
#[xor(2466)]
#[prost(uint32, tag = "15")]
pub ekdhfcplchk: u32,
pub weapon_uid: u32,
#[xor(7457)]
#[prost(uint32, tag = "13")]
pub avatar_id: u32,
@ -22765,7 +22765,7 @@ pub struct GetItemDataScRsp {
#[prost(uint32, repeated, tag = "3")]
pub ohiggablenk: ::prost::alloc::vec::Vec<u32>,
#[prost(message, repeated, tag = "12")]
pub gpdcidjenda: ::prost::alloc::vec::Vec<Mdilomeanog>,
pub weapon_list: ::prost::alloc::vec::Vec<WeaponInfo>,
#[xor(2897)]
#[prost(int32, tag = "5")]
pub retcode: i32,
@ -24358,7 +24358,7 @@ pub struct AvatarInfo {
pub kjfpeockbjd: ::prost::alloc::vec::Vec<Idgpccghdhp>,
#[xor(3463)]
#[prost(uint32, tag = "4")]
pub oafocbpemhm: u32,
pub cur_weapon_uid: u32,
#[xor(10519)]
#[prost(uint32, tag = "1")]
pub level: u32,
@ -29455,7 +29455,7 @@ pub struct Llepiillafj {
pub ihgcjhffkdf: u32,
#[xor(3151)]
#[prost(uint32, tag = "4")]
pub ekdhfcplchk: u32,
pub weapon_uid: u32,
}
#[derive(proto_gen::CmdID)]
#[derive(proto_gen::XorFields)]
@ -33533,7 +33533,7 @@ pub struct Lolghlmjgko {
#[prost(uint32, tag = "5")]
pub unlocked_talent_num: u32,
#[prost(message, optional, tag = "10")]
pub njacjmicemg: ::core::option::Option<Mdilomeanog>,
pub njacjmicemg: ::core::option::Option<WeaponInfo>,
}
#[derive(proto_gen::CmdID)]
#[derive(proto_gen::XorFields)]
@ -34143,7 +34143,7 @@ pub struct Pihldgnlcpl {
#[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Aihacppgmff {
pub struct WeaponUnDressScRsp {
#[xor(1692)]
#[prost(int32, tag = "10")]
pub retcode: i32,
@ -34287,7 +34287,7 @@ pub struct Pcagcihalmf {
#[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Djghjioiild {
pub struct WeaponDressScRsp {
#[xor(4000)]
#[prost(int32, tag = "4")]
pub retcode: i32,

View file

@ -44,6 +44,8 @@ pub struct AvatarBin {
pub rank: u32,
#[prost(uint32, tag = "6")]
pub unlocked_talent_num: u32,
#[prost(uint32, tag = "9")]
pub weapon_uid: u32,
#[prost(message, repeated, tag = "7")]
pub avatar_skill_list: ::prost::alloc::vec::Vec<AvatarSkillBin>,
#[prost(bool, repeated, tag = "8")]
@ -65,9 +67,29 @@ pub struct ResourceItemBin {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WeaponBin {
#[prost(uint32, tag = "1")]
pub template_id: u32,
#[prost(uint32, tag = "2")]
pub uid: u32,
#[prost(uint32, tag = "3")]
pub level: u32,
#[prost(uint32, tag = "4")]
pub exp: u32,
#[prost(uint32, tag = "5")]
pub star: u32,
#[prost(uint32, tag = "6")]
pub refine_level: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ItemModelBin {
#[prost(message, repeated, tag = "1")]
pub resource_list: ::prost::alloc::vec::Vec<ResourceItemBin>,
#[prost(message, repeated, tag = "2")]
pub weapon_list: ::prost::alloc::vec::Vec<WeaponBin>,
#[prost(uint32, tag = "10")]
pub item_uid_counter: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]