newtype fun

use newtypes for template ids, pros:
1) enforces id validation
2) ease of use (helper method for getting template struct right away)
This commit is contained in:
xeon 2024-07-25 01:24:48 +03:00
parent 69634fda64
commit 99123a15ef
30 changed files with 228 additions and 136 deletions

View file

@ -1,10 +1,12 @@
use serde::Deserialize; use serde::Deserialize;
template_id!(AvatarBase u32 id);
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct AvatarBaseTemplate { pub struct AvatarBaseTemplate {
#[serde(rename = "ID")] #[serde(rename = "ID")]
pub id: u32, pub id: AvatarBaseID,
pub code_name: String, pub code_name: String,
pub name: String, pub name: String,
pub full_name: String, pub full_name: String,

View file

@ -3,11 +3,49 @@ use std::sync::OnceLock;
use super::DataLoadError; use super::DataLoadError;
macro_rules! template_id {
($type_name:ident $underlying_type:ident $id_field:ident) => {
::paste::paste! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ::serde::Deserialize, ::serde::Serialize)]
pub struct [<$type_name ID>]($underlying_type);
impl [<$type_name ID>] {
pub fn new(id: $underlying_type) -> Option<Self> {
if crate::tables::[<$type_name:snake _template_tb>]::iter().any(|tmpl| tmpl.$id_field.value() == id) {
Some(Self(id))
}
else {
None
}
}
pub fn new_unchecked(id: $underlying_type) -> Self {
Self(id)
}
pub fn value(&self) -> $underlying_type {
self.0
}
pub fn template(&self) -> &[<$type_name Template>] {
crate::tables::[<$type_name:snake _template_tb>]::iter().find(|tmpl| tmpl.$id_field == *self).unwrap()
}
}
impl ::std::fmt::Display for [<$type_name ID>] {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.write_fmt(format_args!("{}", self.0))
}
}
}
};
}
macro_rules! template_tables { macro_rules! template_tables {
($($template_type:ident;)*) => { ($($template_type:ident;)*) => {
$(paste! { $(paste! {
mod [<$template_type:snake>]; mod [<$template_type:snake>];
pub use [<$template_type:snake>]::$template_type; pub use [<$template_type:snake>]::*;
})* })*
$(paste! { $(paste! {

View file

@ -1,9 +1,11 @@
use serde::Deserialize; use serde::Deserialize;
template_id!(PostGirlConfig u32 id);
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct PostGirlConfigTemplate { pub struct PostGirlConfigTemplate {
#[serde(rename = "ID")] #[serde(rename = "ID")]
pub id: u32, pub id: PostGirlConfigID,
pub name: String, pub name: String,
} }

View file

@ -1,14 +1,16 @@
use serde::Deserialize; use serde::Deserialize;
template_id!(ProcedureConfig u32 procedure_id);
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct ProcedureConfigTemplate { pub struct ProcedureConfigTemplate {
#[serde(rename = "ProcedureID")] #[serde(rename = "ProcedureID")]
pub procedure_id: i32, pub procedure_id: ProcedureConfigID,
pub procedure_type: u32, pub procedure_type: u32,
#[serde(rename = "ContentID")] #[serde(rename = "ContentID")]
pub content_id: String, pub content_id: String,
pub jump_tos: Vec<i32>, pub jump_tos: Vec<u32>,
pub procedure_banks: Vec<String>, pub procedure_banks: Vec<String>,
pub procedure_event: String, pub procedure_event: String,
} }

View file

@ -1,9 +1,11 @@
use serde::Deserialize; use serde::Deserialize;
template_id!(SectionConfig u32 section_id);
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct SectionConfigTemplate { pub struct SectionConfigTemplate {
pub section_id: u32, pub section_id: SectionConfigID,
pub photo_name: String, pub photo_name: String,
pub name: String, pub name: String,
pub primary_entry_name: String, pub primary_entry_name: String,

View file

@ -1,9 +1,11 @@
use serde::Deserialize; use serde::Deserialize;
template_id!(TrainingQuest u32 id);
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct TrainingQuestTemplate { pub struct TrainingQuestTemplate {
pub id: u32, pub id: TrainingQuestID,
pub training_type: u32, pub training_type: u32,
pub battle_event_id: u32, pub battle_event_id: u32,
} }

View file

@ -1,9 +1,11 @@
use serde::Deserialize; use serde::Deserialize;
template_id!(UnlockConfig i32 id);
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct UnlockConfigTemplate { pub struct UnlockConfigTemplate {
#[serde(rename = "ID")] #[serde(rename = "ID")]
pub id: i32, pub id: UnlockConfigID,
pub name: String, pub name: String,
} }

View file

@ -1,10 +1,12 @@
use serde::Deserialize; use serde::Deserialize;
template_id!(Weapon u32 item_id);
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct WeaponTemplate { pub struct WeaponTemplate {
#[serde(rename = "ItemID")] #[serde(rename = "ItemID")]
pub item_id: u32, pub item_id: WeaponID,
pub code_name: String, pub code_name: String,
#[serde(rename = "Type")] #[serde(rename = "Type")]
pub weapon_type: u32, pub weapon_type: u32,

View file

@ -1,4 +1,4 @@
use data::tables; use data::tables::AvatarBaseID;
use proto::{AddAvatarPerformType, AddAvatarScNotify, PlayerSyncScNotify}; use proto::{AddAvatarPerformType, AddAvatarScNotify, PlayerSyncScNotify};
use crate::ServerState; use crate::ServerState;
@ -17,15 +17,14 @@ pub async fn add(
let uid = args[0].parse::<u32>()?; let uid = args[0].parse::<u32>()?;
let avatar_id = args[1].parse::<u32>()?; let avatar_id = args[1].parse::<u32>()?;
let Some(avatar_id) = AvatarBaseID::new(avatar_id) else {
return Ok(format!("avatar with id {avatar_id} doesn't exist"));
};
let Some(player_lock) = state.player_mgr.get_player(uid).await else { let Some(player_lock) = state.player_mgr.get_player(uid).await else {
return Ok(String::from("player not found")); return Ok(String::from("player not found"));
}; };
if !tables::avatar_base_template_tb::iter().any(|tmpl| tmpl.id == avatar_id) {
return Ok(format!("avatar with id {avatar_id} doesn't exist"));
}
let (session_id, avatar_sync) = { let (session_id, avatar_sync) = {
let mut player = player_lock.lock().await; let mut player = player_lock.lock().await;
@ -36,7 +35,7 @@ pub async fn add(
if let Some(session) = session_id.map(|id| state.session_mgr.get(id)).flatten() { if let Some(session) = session_id.map(|id| state.session_mgr.get(id)).flatten() {
session session
.notify(AddAvatarScNotify { .notify(AddAvatarScNotify {
avatar_id, avatar_id: avatar_id.value(),
perform_type: AddAvatarPerformType::Gacha.into(), perform_type: AddAvatarPerformType::Gacha.into(),
..Default::default() ..Default::default()
}) })

View file

@ -1,4 +1,4 @@
use data::tables; use data::tables::WeaponID;
use proto::PlayerSyncScNotify; use proto::PlayerSyncScNotify;
use crate::ServerState; use crate::ServerState;
@ -17,10 +17,9 @@ pub async fn add_weapon(
let uid = args[0].parse::<u32>()?; let uid = args[0].parse::<u32>()?;
let weapon_id = args[1].parse::<u32>()?; let weapon_id = args[1].parse::<u32>()?;
let Some(weapon_id) = WeaponID::new(weapon_id) else {
if !tables::weapon_template_tb::iter().any(|tmpl| tmpl.item_id == weapon_id) {
return Ok(format!("weapon with id {weapon_id} doesn't exist")); return Ok(format!("weapon with id {weapon_id} doesn't exist"));
} };
let Some(player_lock) = state.player_mgr.get_player(uid).await else { let Some(player_lock) = state.player_mgr.get_player(uid).await else {
return Ok(String::from("player not found")); return Ok(String::from("player not found"));

View file

@ -1,4 +1,4 @@
use data::tables; use data::tables::{AvatarBaseID, ProcedureConfigID};
use proto::PlayerSyncScNotify; use proto::PlayerSyncScNotify;
use crate::ServerState; use crate::ServerState;
@ -17,18 +17,17 @@ pub async fn avatar(
let uid = args[0].parse::<u32>()?; let uid = args[0].parse::<u32>()?;
let avatar_id = args[1].parse::<u32>()?; let avatar_id = args[1].parse::<u32>()?;
let Some(avatar_id) = AvatarBaseID::new(avatar_id) else {
return Ok(format!("avatar with id {avatar_id} doesn't exist"));
};
let Some(player_lock) = state.player_mgr.get_player(uid).await else { let Some(player_lock) = state.player_mgr.get_player(uid).await else {
return Ok(String::from("player not found")); return Ok(String::from("player not found"));
}; };
if !tables::avatar_base_template_tb::iter().any(|tmpl| tmpl.id == avatar_id) {
return Ok(format!("avatar with id {avatar_id} doesn't exist"));
}
let should_save = { let should_save = {
let mut player = player_lock.lock().await; let mut player = player_lock.lock().await;
player.basic_data_model.frontend_avatar_id = avatar_id as i32; player.basic_data_model.frontend_avatar_id = avatar_id.value() as i32;
player.current_session_id().is_none() player.current_session_id().is_none()
}; };
@ -103,12 +102,10 @@ pub async fn procedure(
let uid = args[0].parse::<u32>()?; let uid = args[0].parse::<u32>()?;
let procedure_id = args[1].parse::<i32>()?; let procedure_id = args[1].parse::<i32>()?;
if procedure_id != -1 let procedure_id = match procedure_id {
&& !tables::procedure_config_template_tb::iter() 1.. => ProcedureConfigID::new(procedure_id as u32),
.any(|tmpl| tmpl.procedure_id == procedure_id) _ => None,
{ };
return Ok(format!("procedure_id {procedure_id} doesn't exist"));
}
let Some(player_lock) = state.player_mgr.get_player(uid).await else { let Some(player_lock) = state.player_mgr.get_player(uid).await else {
return Ok(String::from("player not found")); return Ok(String::from("player not found"));
@ -126,6 +123,6 @@ pub async fn procedure(
} }
Ok(format!( Ok(format!(
"successfully changed procedure_id to {procedure_id}" "successfully changed procedure_id to {procedure_id:?}"
)) ))
} }

View file

@ -26,11 +26,11 @@ pub async fn on_get_client_systems_info(
info: Some(ClientSystemsInfo { info: Some(ClientSystemsInfo {
post_girl_data: Some(PostGirlData { post_girl_data: Some(PostGirlData {
selected_post_girl_id_list: tables::post_girl_config_template_tb::iter() selected_post_girl_id_list: tables::post_girl_config_template_tb::iter()
.map(|template| template.id) .map(|template| template.id.value())
.collect(), .collect(),
post_girl_list: tables::post_girl_config_template_tb::iter() post_girl_list: tables::post_girl_config_template_tb::iter()
.map(|template| PostGirlItem { .map(|template| PostGirlItem {
template_id: template.id, template_id: template.id.value(),
unlock_time: 1000, unlock_time: 1000,
}) })
.collect(), .collect(),

View file

@ -1,3 +1,5 @@
use data::tables::AvatarBaseID;
use super::*; use super::*;
pub async fn on_get_item_data( pub async fn on_get_item_data(
@ -20,7 +22,10 @@ pub async fn on_weapon_dress(
player: &mut Player, player: &mut Player,
req: WeaponDressCsReq, req: WeaponDressCsReq,
) -> NetResult<WeaponDressScRsp> { ) -> NetResult<WeaponDressScRsp> {
player.dress_weapon(req.avatar_id, req.weapon_uid.into())?; player.dress_weapon(
AvatarBaseID::new(req.avatar_id).ok_or(Retcode::RetFail)?,
req.weapon_uid.into(),
)?;
session session
.notify(PlayerSyncScNotify { .notify(PlayerSyncScNotify {
@ -40,11 +45,12 @@ pub async fn on_weapon_un_dress(
player: &mut Player, player: &mut Player,
req: WeaponUnDressCsReq, req: WeaponUnDressCsReq,
) -> NetResult<WeaponUnDressScRsp> { ) -> NetResult<WeaponUnDressScRsp> {
let avatar_id = AvatarBaseID::new(req.avatar_id).ok_or(Retcode::RetFail)?;
let avatar = player let avatar = player
.role_model .role_model
.avatar_list .avatar_list
.iter_mut() .iter_mut()
.find(|a| a.template_id == req.avatar_id) .find(|a| a.template_id == avatar_id)
.ok_or(Retcode::RetFail)?; .ok_or(Retcode::RetFail)?;
avatar.weapon_uid = None; avatar.weapon_uid = None;

View file

@ -1,3 +1,5 @@
use data::tables::{ProcedureConfigID, TrainingQuestID};
use super::core::NetError; use super::core::NetError;
use crate::{ use crate::{
@ -14,10 +16,8 @@ pub async fn on_enter_world(
) -> NetResult<EnterWorldScRsp> { ) -> NetResult<EnterWorldScRsp> {
session.set_state(NetSessionState::EndBasicsReq); session.set_state(NetSessionState::EndBasicsReq);
if player.basic_data_model.beginner_procedure_id != -1 { if let Some(procedure_id) = player.basic_data_model.beginner_procedure_id {
player.game_instance = GameInstance::Fresh(FreshGame::new( player.game_instance = GameInstance::Fresh(FreshGame::new(procedure_id))
player.basic_data_model.beginner_procedure_id,
))
} else { } else {
player.init_frontend_game()?; player.init_frontend_game()?;
} }
@ -41,9 +41,12 @@ pub async fn on_advance_beginner_procedure(
return Err(NetError::from(Retcode::RetFail)); return Err(NetError::from(Retcode::RetFail));
}; };
let procedure_id =
ProcedureConfigID::new(req.procedure_id as u32).ok_or(Retcode::RetFail)?;
fresh_game fresh_game
.procedure_mgr .procedure_mgr
.try_complete_procedure(req.procedure_id) .try_complete_procedure(procedure_id)
.map_err(LogicError::from)?; .map_err(LogicError::from)?;
player.basic_data_model.beginner_procedure_id = fresh_game.procedure_mgr.procedure_id(); player.basic_data_model.beginner_procedure_id = fresh_game.procedure_mgr.procedure_id();
@ -160,8 +163,10 @@ pub async fn on_start_trial_fighting_mission(
player: &mut Player, player: &mut Player,
req: StartTrialFightingMissionCsReq, req: StartTrialFightingMissionCsReq,
) -> NetResult<StartTrialFightingMissionScRsp> { ) -> NetResult<StartTrialFightingMissionScRsp> {
let quest_id = TrainingQuestID::new(req.quest_id).ok_or(Retcode::RetFail)?;
player.game_instance = GameInstance::Hollow( player.game_instance = GameInstance::Hollow(
HollowGame::create_training_game(req.quest_id, ELocalPlayType::TrainingRoomFight) HollowGame::create_training_game(quest_id, ELocalPlayType::TrainingRoomFight)
.map_err(LogicError::from)?, .map_err(LogicError::from)?,
); );

View file

@ -1,3 +1,4 @@
use data::tables::ProcedureConfigID;
use proto::{DungeonInfo, FreshSceneInfo, SceneInfo}; use proto::{DungeonInfo, FreshSceneInfo, SceneInfo};
use crate::logic::{procedure::ProcedureManager, ESceneType}; use crate::logic::{procedure::ProcedureManager, ESceneType};
@ -9,7 +10,7 @@ pub struct FreshGame {
} }
impl FreshGame { impl FreshGame {
pub fn new(start_procedure_id: i32) -> Self { pub fn new(start_procedure_id: ProcedureConfigID) -> Self {
Self { Self {
procedure_mgr: ProcedureManager::new(start_procedure_id), procedure_mgr: ProcedureManager::new(start_procedure_id),
} }
@ -25,7 +26,11 @@ impl NapGameMode for FreshGame {
Some(SceneInfo { Some(SceneInfo {
scene_type: self.scene_type() as u32, scene_type: self.scene_type() as u32,
fresh_scene_info: Some(FreshSceneInfo { fresh_scene_info: Some(FreshSceneInfo {
beginner_procedure_id: (self.procedure_mgr.procedure_id() - 1) as u32, beginner_procedure_id: self
.procedure_mgr
.procedure_id()
.map(|i| i.value() - 1)
.unwrap_or(0),
}), }),
..Default::default() ..Default::default()
}) })

View file

@ -1,4 +1,4 @@
use data::tables; use data::tables::SectionConfigID;
use proto::*; use proto::*;
use thiserror::Error; use thiserror::Error;
@ -7,7 +7,7 @@ use crate::logic::{math::Vector3f, time::MainCityTime, ESceneType};
use super::NapGameMode; use super::NapGameMode;
pub struct FrontendGame { pub struct FrontendGame {
section_id: u32, section_id: SectionConfigID,
frontend_avatar_id: i32, frontend_avatar_id: i32,
camera_x: u32, camera_x: u32,
camera_y: u32, camera_y: u32,
@ -18,14 +18,11 @@ pub struct FrontendGame {
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum FrontendGameError { pub enum FrontendGameError {}
#[error("section id is invalid ({0})")]
InvalidSection(u32),
}
impl FrontendGame { impl FrontendGame {
pub fn new( pub fn new(
section_id: u32, section_id: SectionConfigID,
avatar_id: i32, avatar_id: i32,
main_city_time: MainCityTime, main_city_time: MainCityTime,
avatar_pos: Vector3f, avatar_pos: Vector3f,
@ -37,8 +34,7 @@ impl FrontendGame {
frontend_avatar_id: avatar_id, frontend_avatar_id: avatar_id,
camera_x: 0xFFFFFFFF, camera_x: 0xFFFFFFFF,
camera_y: 0xFFFFFFFF, camera_y: 0xFFFFFFFF,
born_pos: Self::get_default_stage_entry_name(section_id) born_pos: section_id.template().primary_entry_name.clone(),
.ok_or(FrontendGameError::InvalidSection(section_id))?,
avatar_pos, avatar_pos,
avatar_rot, avatar_rot,
}; };
@ -46,12 +42,6 @@ impl FrontendGame {
tracing::info!("creating new frontend game (section={section_id}, avatar={avatar_id})"); tracing::info!("creating new frontend game (section={section_id}, avatar={avatar_id})");
Ok(instance) Ok(instance)
} }
fn get_default_stage_entry_name(section_id: u32) -> Option<String> {
tables::section_config_template_tb::iter()
.find(|tmpl| tmpl.section_id == section_id)
.map(|tmpl| tmpl.primary_entry_name.clone())
}
} }
impl NapGameMode for FrontendGame { impl NapGameMode for FrontendGame {
@ -59,7 +49,7 @@ impl NapGameMode for FrontendGame {
Some(SceneInfo { Some(SceneInfo {
scene_type: self.scene_type() as u32, scene_type: self.scene_type() as u32,
hall_scene_info: Some(HallSceneInfo { hall_scene_info: Some(HallSceneInfo {
section_id: self.section_id, section_id: self.section_id.value(),
frontend_avatar_id: self.frontend_avatar_id as u32, frontend_avatar_id: self.frontend_avatar_id as u32,
camera_x: self.camera_x, camera_x: self.camera_x,
camera_y: self.camera_y, camera_y: self.camera_y,

View file

@ -1,5 +1,5 @@
use common::util; use common::util;
use data::tables; use data::tables::TrainingQuestID;
use proto::{DungeonInfo, DungeonItemData, FightSceneInfo, SceneInfo, WeatherPoolInfo}; use proto::{DungeonInfo, DungeonItemData, FightSceneInfo, SceneInfo, WeatherPoolInfo};
use thiserror::Error; use thiserror::Error;
@ -8,10 +8,7 @@ use crate::logic::{ELocalPlayType, ESceneType, TimePeriodType, WeatherType};
use super::NapGameMode; use super::NapGameMode;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum HollowGameError { pub enum HollowGameError {}
#[error("quest id is invalid: {0}")]
InvalidQuestId(u32),
}
pub struct HollowGame { pub struct HollowGame {
pub quest_id: u32, pub quest_id: u32,
@ -24,15 +21,13 @@ pub struct HollowGame {
impl HollowGame { impl HollowGame {
pub fn create_training_game( pub fn create_training_game(
training_quest_id: u32, training_quest_id: TrainingQuestID,
play_type: ELocalPlayType, play_type: ELocalPlayType,
) -> Result<Self, HollowGameError> { ) -> Result<Self, HollowGameError> {
let template = tables::training_quest_template_tb::iter() let template = training_quest_id.template();
.find(|tmpl| tmpl.id == training_quest_id)
.ok_or(HollowGameError::InvalidQuestId(training_quest_id))?;
Ok(Self { Ok(Self {
quest_id: template.id, quest_id: template.id.value(),
battle_event_id: template.battle_event_id, battle_event_id: template.battle_event_id,
time_period: TimePeriodType::Morning, time_period: TimePeriodType::Morning,
weather: WeatherType::SunShine, weather: WeatherType::SunShine,

View file

@ -1,4 +1,5 @@
use super::{ItemUID, ResourceItem, Weapon}; use super::{ItemUID, ResourceItem, Weapon};
use data::tables::WeaponID;
use proto::{ItemModelBin, ItemSync}; use proto::{ItemModelBin, ItemSync};
pub struct ItemModel { pub struct ItemModel {
@ -43,7 +44,7 @@ impl ItemModel {
} }
} }
pub fn add_weapon(&mut self, template_id: u32) -> ItemUID { pub fn add_weapon(&mut self, template_id: WeaponID) -> ItemUID {
let uid = self.next_uid(); let uid = self.next_uid();
self.weapons.push(Weapon::new(template_id, uid)); self.weapons.push(Weapon::new(template_id, uid));

View file

@ -1,11 +1,11 @@
use super::ItemUID; use super::ItemUID;
use data::tables::{self, WeaponTemplate}; use data::tables::WeaponID;
use proto::{WeaponBin, WeaponInfo}; use proto::{WeaponBin, WeaponInfo};
pub struct Weapon { pub struct Weapon {
pub uid: ItemUID, pub uid: ItemUID,
pub template_id: u32, pub template_id: WeaponID,
pub star: u32, pub star: u32,
pub level: u32, pub level: u32,
pub exp: u32, pub exp: u32,
@ -13,8 +13,8 @@ pub struct Weapon {
} }
impl Weapon { impl Weapon {
pub fn new(template_id: u32, uid: ItemUID) -> Self { pub fn new(template_id: WeaponID, uid: ItemUID) -> Self {
let template = Self::get_template(template_id).unwrap(); let template = template_id.template();
Self { Self {
template_id, template_id,
@ -28,7 +28,7 @@ impl Weapon {
pub fn from_bin(bin: WeaponBin) -> Self { pub fn from_bin(bin: WeaponBin) -> Self {
Self { Self {
template_id: bin.template_id, template_id: WeaponID::new_unchecked(bin.template_id),
uid: bin.uid.into(), uid: bin.uid.into(),
star: bin.star, star: bin.star,
level: bin.level, level: bin.level,
@ -39,7 +39,7 @@ impl Weapon {
pub fn to_bin(&self) -> WeaponBin { pub fn to_bin(&self) -> WeaponBin {
WeaponBin { WeaponBin {
template_id: self.template_id, template_id: self.template_id.value(),
uid: self.uid.value(), uid: self.uid.value(),
star: self.star, star: self.star,
level: self.level, level: self.level,
@ -50,7 +50,7 @@ impl Weapon {
pub fn to_client(&self) -> WeaponInfo { pub fn to_client(&self) -> WeaponInfo {
WeaponInfo { WeaponInfo {
template_id: self.template_id, template_id: self.template_id.value(),
uid: self.uid.value(), uid: self.uid.value(),
star: self.star, star: self.star,
level: self.level, level: self.level,
@ -59,8 +59,4 @@ impl Weapon {
..Default::default() ..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

@ -1,3 +1,4 @@
use data::tables::ProcedureConfigID;
use proto::{BasicDataModelBin, PlayerBasicInfo}; use proto::{BasicDataModelBin, PlayerBasicInfo};
pub struct BasicDataModel { pub struct BasicDataModel {
@ -6,7 +7,7 @@ pub struct BasicDataModel {
pub profile_icon: u32, pub profile_icon: u32,
pub nick_name: Option<String>, pub nick_name: Option<String>,
pub frontend_avatar_id: i32, pub frontend_avatar_id: i32,
pub beginner_procedure_id: i32, pub beginner_procedure_id: Option<ProcedureConfigID>,
} }
impl Default for BasicDataModel { impl Default for BasicDataModel {
@ -17,7 +18,7 @@ impl Default for BasicDataModel {
profile_icon: 3200000, profile_icon: 3200000,
nick_name: None, nick_name: None,
frontend_avatar_id: 0, frontend_avatar_id: 0,
beginner_procedure_id: 1, beginner_procedure_id: Some(ProcedureConfigID::new_unchecked(1)),
} }
} }
} }
@ -41,7 +42,10 @@ impl BasicDataModel {
exp: bin.exp, exp: bin.exp,
profile_icon: bin.profile_icon, profile_icon: bin.profile_icon,
frontend_avatar_id: bin.frontend_avatar_id, frontend_avatar_id: bin.frontend_avatar_id,
beginner_procedure_id: bin.beginner_procedure_id, beginner_procedure_id: match bin.beginner_procedure_id {
1.. => ProcedureConfigID::new(bin.beginner_procedure_id as u32),
_ => None,
},
nick_name: match bin.nick_name.is_empty() { nick_name: match bin.nick_name.is_empty() {
true => None, true => None,
false => Some(bin.nick_name), false => Some(bin.nick_name),
@ -56,7 +60,10 @@ impl BasicDataModel {
profile_icon: self.profile_icon, profile_icon: self.profile_icon,
frontend_avatar_id: self.frontend_avatar_id, frontend_avatar_id: self.frontend_avatar_id,
nick_name: self.nick_name.clone().unwrap_or_default(), nick_name: self.nick_name.clone().unwrap_or_default(),
beginner_procedure_id: self.beginner_procedure_id, beginner_procedure_id: self
.beginner_procedure_id
.map(|i| i.value() as i32)
.unwrap_or(-1),
} }
} }
} }

View file

@ -1,37 +1,52 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use data::tables::UnlockConfigID;
use proto::{LockModelBin, UnlockData}; use proto::{LockModelBin, UnlockData};
#[derive(Default)] #[derive(Default)]
pub struct LockModel { pub struct LockModel {
unlock_list: BTreeSet<i32>, unlock_list: BTreeSet<UnlockConfigID>,
} }
impl LockModel { impl LockModel {
pub fn from_bin(bin: LockModelBin) -> Self { pub fn from_bin(bin: LockModelBin) -> Self {
Self { Self {
unlock_list: bin.unlock_list.into_iter().collect(), unlock_list: bin
.unlock_list
.into_iter()
.map(UnlockConfigID::new_unchecked)
.collect(),
} }
} }
pub fn to_bin(&self) -> LockModelBin { pub fn to_bin(&self) -> LockModelBin {
LockModelBin { LockModelBin {
unlock_list: self.unlock_list.clone().into_iter().collect(), unlock_list: self
.unlock_list
.clone()
.into_iter()
.map(|i| i.value())
.collect(),
} }
} }
pub fn to_client(&self) -> UnlockData { pub fn to_client(&self) -> UnlockData {
UnlockData { UnlockData {
unlock_id_list: self.unlock_list.clone().into_iter().collect(), unlock_id_list: self
.unlock_list
.clone()
.into_iter()
.map(|i| i.value())
.collect(),
..Default::default() ..Default::default()
} }
} }
pub fn add_unlock(&mut self, id: i32) { pub fn add_unlock(&mut self, id: UnlockConfigID) {
self.unlock_list.insert(id); self.unlock_list.insert(id);
} }
pub fn is_unlock(&self, id: i32) -> bool { pub fn is_unlock(&self, id: UnlockConfigID) -> bool {
self.unlock_list.contains(&id) self.unlock_list.contains(&id)
} }
} }

View file

@ -1,3 +1,4 @@
use data::tables::SectionConfigID;
use proto::{MainCityModelBin, TransformBin}; use proto::{MainCityModelBin, TransformBin};
use super::{math::Vector3f, time::MainCityTime}; use super::{math::Vector3f, time::MainCityTime};
@ -6,7 +7,7 @@ pub struct MainCityModel {
pub position: Vector3f, pub position: Vector3f,
pub rotation: Vector3f, pub rotation: Vector3f,
pub main_city_time: MainCityTime, pub main_city_time: MainCityTime,
pub section_id: u32, pub section_id: SectionConfigID,
} }
impl MainCityModel { impl MainCityModel {
@ -22,7 +23,7 @@ impl MainCityModel {
rotation: self.position.to_vec(), rotation: self.position.to_vec(),
}), }),
time: Some(self.main_city_time.to_bin()), time: Some(self.main_city_time.to_bin()),
section_id: self.section_id, section_id: self.section_id.value(),
} }
} }
@ -33,7 +34,7 @@ impl MainCityModel {
position: Vector3f::from_vec(transform.position), position: Vector3f::from_vec(transform.position),
rotation: Vector3f::from_vec(transform.rotation), rotation: Vector3f::from_vec(transform.rotation),
main_city_time: bin.time.map(MainCityTime::from_bin).unwrap_or_default(), main_city_time: bin.time.map(MainCityTime::from_bin).unwrap_or_default(),
section_id: bin.section_id, section_id: SectionConfigID::new_unchecked(bin.section_id),
} }
} }
} }
@ -44,7 +45,7 @@ impl Default for MainCityModel {
position: Vector3f::default(), position: Vector3f::default(),
rotation: Vector3f::default(), rotation: Vector3f::default(),
main_city_time: MainCityTime::default(), main_city_time: MainCityTime::default(),
section_id: 1, section_id: SectionConfigID::new_unchecked(1),
} }
} }
} }

View file

@ -1,4 +1,4 @@
use data::tables; use data::tables::{self, AvatarBaseID};
use proto::{ItemStatic, PlayerDataBin, Retcode}; use proto::{ItemStatic, PlayerDataBin, Retcode};
use super::game::{FrontendGame, GameInstance, LogicError}; use super::game::{FrontendGame, GameInstance, LogicError};
@ -90,7 +90,11 @@ impl Player {
Ok(()) Ok(())
} }
pub fn dress_weapon(&mut self, avatar_id: u32, weapon_uid: ItemUID) -> Result<(), Retcode> { pub fn dress_weapon(
&mut self,
avatar_id: AvatarBaseID,
weapon_uid: ItemUID,
) -> Result<(), Retcode> {
self.item_model self.item_model
.weapons .weapons
.iter() .iter()

View file

@ -1,12 +1,14 @@
use data::tables::ProcedureConfigID;
use super::{ProcedureAction, ProcedureBase, ProcedureError, ProcedureState, ProcedureType}; use super::{ProcedureAction, ProcedureBase, ProcedureError, ProcedureState, ProcedureType};
pub struct ProcedureBattle { pub struct ProcedureBattle {
id: i32, id: ProcedureConfigID,
state: ProcedureState, state: ProcedureState,
} }
impl ProcedureBattle { impl ProcedureBattle {
pub fn new(id: i32) -> Self { pub fn new(id: ProcedureConfigID) -> Self {
Self { Self {
id, id,
state: ProcedureState::Init, state: ProcedureState::Init,
@ -15,7 +17,7 @@ impl ProcedureBattle {
} }
impl ProcedureBase for ProcedureBattle { impl ProcedureBase for ProcedureBattle {
fn id(&self) -> i32 { fn id(&self) -> ProcedureConfigID {
self.id self.id
} }

View file

@ -8,7 +8,7 @@ pub use plot_play::ProcedurePlotPlay;
pub use procedure_mgr::ProcedureManager; pub use procedure_mgr::ProcedureManager;
pub use select_role::ProcedureSelectRole; pub use select_role::ProcedureSelectRole;
use data::tables::{self, ProcedureConfigTemplate}; use data::tables::{self, ProcedureConfigID, ProcedureConfigTemplate};
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use thiserror::Error; use thiserror::Error;
@ -45,21 +45,25 @@ pub enum ProcedureError {
#[error("can't advance procedure because it's not finished")] #[error("can't advance procedure because it's not finished")]
NotFinished, NotFinished,
#[error("trying to complete procedure: {0}, current procedure: {1}")] #[error("trying to complete procedure: {0}, current procedure: {1}")]
InvalidProcedureId(i32, i32), InvalidProcedureId(ProcedureConfigID, ProcedureConfigID),
#[error("current procedure is NULL!")] #[error("current procedure is NULL!")]
ProcedureIsNull, ProcedureIsNull,
} }
pub trait ProcedureBase { pub trait ProcedureBase {
fn id(&self) -> i32; fn id(&self) -> ProcedureConfigID;
fn procedure_type(&self) -> ProcedureType; fn procedure_type(&self) -> ProcedureType;
fn get_next_id(&self) -> Option<&i32> { fn get_next_id(&self) -> Option<ProcedureConfigID> {
let config = tables::procedure_config_template_tb::iter() let config = tables::procedure_config_template_tb::iter()
.find(|tmpl| tmpl.procedure_id == self.id()) .find(|tmpl| tmpl.procedure_id == self.id())
.unwrap(); .unwrap();
config.jump_tos.iter().next() config
.jump_tos
.iter()
.next()
.map(|id| ProcedureConfigID::new_unchecked(*id))
} }
fn on_action(&mut self, action: ProcedureAction) -> Result<ProcedureState, ProcedureError>; fn on_action(&mut self, action: ProcedureAction) -> Result<ProcedureState, ProcedureError>;

View file

@ -1,12 +1,14 @@
use data::tables::ProcedureConfigID;
use super::{ProcedureAction, ProcedureBase, ProcedureError, ProcedureState, ProcedureType}; use super::{ProcedureAction, ProcedureBase, ProcedureError, ProcedureState, ProcedureType};
pub struct ProcedurePlotPlay { pub struct ProcedurePlotPlay {
id: i32, id: ProcedureConfigID,
state: ProcedureState, state: ProcedureState,
} }
impl ProcedurePlotPlay { impl ProcedurePlotPlay {
pub fn new(id: i32) -> Self { pub fn new(id: ProcedureConfigID) -> Self {
Self { Self {
id, id,
state: ProcedureState::Init, state: ProcedureState::Init,
@ -15,7 +17,7 @@ impl ProcedurePlotPlay {
} }
impl ProcedureBase for ProcedurePlotPlay { impl ProcedureBase for ProcedurePlotPlay {
fn id(&self) -> i32 { fn id(&self) -> ProcedureConfigID {
self.id self.id
} }

View file

@ -1,30 +1,34 @@
use data::tables; use data::tables::{self, ProcedureConfigID};
use super::{Procedure, ProcedureAction, ProcedureError}; use super::{Procedure, ProcedureAction, ProcedureError};
pub struct ProcedureManager { pub struct ProcedureManager {
cur_procedure_id: i32, cur_procedure_id: Option<ProcedureConfigID>,
procedures: Vec<Procedure>, procedures: Vec<Procedure>,
} }
impl ProcedureManager { impl ProcedureManager {
pub fn new(start_procedure_id: i32) -> Self { pub fn new(start_procedure_id: ProcedureConfigID) -> Self {
Self { Self {
cur_procedure_id: start_procedure_id, cur_procedure_id: Some(start_procedure_id),
procedures: tables::procedure_config_template_tb::iter() procedures: tables::procedure_config_template_tb::iter()
.filter(|tmpl| tmpl.procedure_id >= start_procedure_id) .filter(|tmpl| tmpl.procedure_id.value() >= start_procedure_id.value())
.map(Procedure::new) .map(Procedure::new)
.collect(), .collect(),
} }
} }
pub fn try_complete_procedure(&mut self, procedure_id: i32) -> Result<(), ProcedureError> { pub fn try_complete_procedure(
(self.cur_procedure_id == procedure_id) &mut self,
.then_some(()) procedure_id: ProcedureConfigID,
.ok_or(ProcedureError::InvalidProcedureId( ) -> Result<(), ProcedureError> {
procedure_id, let Some(cur_procedure_id) = self.cur_procedure_id else {
self.cur_procedure_id, return Err(ProcedureError::ProcedureIsNull);
))?; };
(cur_procedure_id == procedure_id).then_some(()).ok_or(
ProcedureError::InvalidProcedureId(procedure_id, cur_procedure_id),
)?;
let procedure = self let procedure = self
.procedures .procedures
@ -33,7 +37,7 @@ impl ProcedureManager {
.ok_or(ProcedureError::ProcedureIsNull)?; .ok_or(ProcedureError::ProcedureIsNull)?;
if procedure.base().is_finished() { if procedure.base().is_finished() {
self.cur_procedure_id = procedure.base().get_next_id().cloned().unwrap_or(-1); self.cur_procedure_id = procedure.base().get_next_id();
Ok(()) Ok(())
} else { } else {
Err(ProcedureError::NotFinished) Err(ProcedureError::NotFinished)
@ -41,26 +45,30 @@ impl ProcedureManager {
} }
pub fn on_action(&mut self, action: ProcedureAction) -> Result<(), ProcedureError> { pub fn on_action(&mut self, action: ProcedureAction) -> Result<(), ProcedureError> {
let Some(cur_procedure_id) = self.cur_procedure_id else {
return Err(ProcedureError::ProcedureIsNull);
};
let procedure = self let procedure = self
.procedures .procedures
.iter_mut() .iter_mut()
.find(|proc| proc.base().id() == self.cur_procedure_id) .find(|proc| proc.base().id() == cur_procedure_id)
.ok_or(ProcedureError::ProcedureIsNull)?; .ok_or(ProcedureError::ProcedureIsNull)?;
let state = procedure.base_mut().on_action(action)?; let state = procedure.base_mut().on_action(action)?;
tracing::info!( tracing::info!(
"procedure action {action:?} performed, state: {state:?}, procedure id: {}", "procedure action {action:?} performed, state: {state:?}, procedure id: {}",
self.cur_procedure_id cur_procedure_id
); );
Ok(()) Ok(())
} }
pub fn procedure_id(&self) -> i32 { pub fn procedure_id(&self) -> Option<ProcedureConfigID> {
self.cur_procedure_id self.cur_procedure_id
} }
pub fn is_end(&self) -> bool { pub fn is_end(&self) -> bool {
self.cur_procedure_id == -1 self.cur_procedure_id.is_none()
} }
} }

View file

@ -1,12 +1,14 @@
use data::tables::ProcedureConfigID;
use super::{ProcedureAction, ProcedureBase, ProcedureError, ProcedureState, ProcedureType}; use super::{ProcedureAction, ProcedureBase, ProcedureError, ProcedureState, ProcedureType};
pub struct ProcedureSelectRole { pub struct ProcedureSelectRole {
id: i32, id: ProcedureConfigID,
state: ProcedureState, state: ProcedureState,
} }
impl ProcedureSelectRole { impl ProcedureSelectRole {
pub fn new(id: i32) -> Self { pub fn new(id: ProcedureConfigID) -> Self {
Self { Self {
id, id,
state: ProcedureState::Init, state: ProcedureState::Init,
@ -15,7 +17,7 @@ impl ProcedureSelectRole {
} }
impl ProcedureBase for ProcedureSelectRole { impl ProcedureBase for ProcedureSelectRole {
fn id(&self) -> i32 { fn id(&self) -> ProcedureConfigID {
self.id self.id
} }

View file

@ -1,3 +1,4 @@
use data::tables::AvatarBaseID;
use proto::{AvatarBin, AvatarInfo, AvatarSkillInfo}; use proto::{AvatarBin, AvatarInfo, AvatarSkillInfo};
use crate::logic::item::ItemUID; use crate::logic::item::ItemUID;
@ -6,7 +7,7 @@ use super::AvatarSkill;
pub const AVATAR_TALENT_COUNT: usize = 6; pub const AVATAR_TALENT_COUNT: usize = 6;
pub struct Avatar { pub struct Avatar {
pub template_id: u32, pub template_id: AvatarBaseID,
pub level: u32, pub level: u32,
pub exp: u32, pub exp: u32,
pub star: u32, pub star: u32,
@ -18,7 +19,7 @@ pub struct Avatar {
} }
impl Avatar { impl Avatar {
pub fn new(template_id: u32) -> Self { pub fn new(template_id: AvatarBaseID) -> Self {
Self { Self {
template_id, template_id,
level: 60, level: 60,
@ -39,7 +40,7 @@ impl Avatar {
pub fn from_bin(bin: AvatarBin) -> Self { pub fn from_bin(bin: AvatarBin) -> Self {
Self { Self {
template_id: bin.template_id, template_id: AvatarBaseID::new_unchecked(bin.template_id),
level: bin.level, level: bin.level,
exp: bin.exp, exp: bin.exp,
star: bin.star, star: bin.star,
@ -60,7 +61,7 @@ impl Avatar {
pub fn to_bin(&self) -> AvatarBin { pub fn to_bin(&self) -> AvatarBin {
AvatarBin { AvatarBin {
template_id: self.template_id, template_id: self.template_id.value(),
exp: self.exp, exp: self.exp,
level: self.level, level: self.level,
star: self.star, star: self.star,
@ -74,7 +75,7 @@ impl Avatar {
pub fn to_client(&self) -> AvatarInfo { pub fn to_client(&self) -> AvatarInfo {
AvatarInfo { AvatarInfo {
template_id: self.template_id, template_id: self.template_id.value(),
level: self.level, level: self.level,
skill_list: self skill_list: self
.skill_list .skill_list

View file

@ -1,3 +1,4 @@
use data::tables::AvatarBaseID;
use proto::{AvatarSync, RoleModelBin}; use proto::{AvatarSync, RoleModelBin};
use crate::logic::role::Avatar; use crate::logic::role::Avatar;
@ -11,7 +12,7 @@ impl Default for RoleModel {
Self { Self {
avatar_list: Self::DEFAULT_AVATARS avatar_list: Self::DEFAULT_AVATARS
.iter() .iter()
.map(|tmpl_id| Avatar::new(*tmpl_id)) .map(|tmpl_id| Avatar::new(AvatarBaseID::new_unchecked(*tmpl_id)))
.collect(), .collect(),
} }
} }
@ -20,7 +21,7 @@ impl Default for RoleModel {
impl RoleModel { impl RoleModel {
const DEFAULT_AVATARS: [u32; 2] = [1011, 1081]; const DEFAULT_AVATARS: [u32; 2] = [1011, 1081];
pub fn add_avatar(&mut self, template_id: u32) { pub fn add_avatar(&mut self, template_id: AvatarBaseID) {
if !self if !self
.avatar_list .avatar_list
.iter() .iter()