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;
template_id!(AvatarBase u32 id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct AvatarBaseTemplate {
#[serde(rename = "ID")]
pub id: u32,
pub id: AvatarBaseID,
pub code_name: String,
pub name: String,
pub full_name: String,

View file

@ -3,11 +3,49 @@ use std::sync::OnceLock;
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 {
($($template_type:ident;)*) => {
$(paste! {
mod [<$template_type:snake>];
pub use [<$template_type:snake>]::$template_type;
pub use [<$template_type:snake>]::*;
})*
$(paste! {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use data::tables;
use data::tables::AvatarBaseID;
use proto::{AddAvatarPerformType, AddAvatarScNotify, PlayerSyncScNotify};
use crate::ServerState;
@ -17,15 +17,14 @@ pub async fn add(
let uid = args[0].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 {
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 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() {
session
.notify(AddAvatarScNotify {
avatar_id,
avatar_id: avatar_id.value(),
perform_type: AddAvatarPerformType::Gacha.into(),
..Default::default()
})

View file

@ -1,4 +1,4 @@
use data::tables;
use data::tables::WeaponID;
use proto::PlayerSyncScNotify;
use crate::ServerState;
@ -17,10 +17,9 @@ pub async fn add_weapon(
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) {
let Some(weapon_id) = WeaponID::new(weapon_id) else {
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"));

View file

@ -1,4 +1,4 @@
use data::tables;
use data::tables::{AvatarBaseID, ProcedureConfigID};
use proto::PlayerSyncScNotify;
use crate::ServerState;
@ -17,18 +17,17 @@ pub async fn avatar(
let uid = args[0].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 {
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 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()
};
@ -103,12 +102,10 @@ pub async fn procedure(
let uid = args[0].parse::<u32>()?;
let procedure_id = args[1].parse::<i32>()?;
if procedure_id != -1
&& !tables::procedure_config_template_tb::iter()
.any(|tmpl| tmpl.procedure_id == procedure_id)
{
return Ok(format!("procedure_id {procedure_id} doesn't exist"));
}
let procedure_id = match procedure_id {
1.. => ProcedureConfigID::new(procedure_id as u32),
_ => None,
};
let Some(player_lock) = state.player_mgr.get_player(uid).await else {
return Ok(String::from("player not found"));
@ -126,6 +123,6 @@ pub async fn procedure(
}
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 {
post_girl_data: Some(PostGirlData {
selected_post_girl_id_list: tables::post_girl_config_template_tb::iter()
.map(|template| template.id)
.map(|template| template.id.value())
.collect(),
post_girl_list: tables::post_girl_config_template_tb::iter()
.map(|template| PostGirlItem {
template_id: template.id,
template_id: template.id.value(),
unlock_time: 1000,
})
.collect(),

View file

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

View file

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

View file

@ -1,3 +1,4 @@
use data::tables::ProcedureConfigID;
use proto::{DungeonInfo, FreshSceneInfo, SceneInfo};
use crate::logic::{procedure::ProcedureManager, ESceneType};
@ -9,7 +10,7 @@ pub struct FreshGame {
}
impl FreshGame {
pub fn new(start_procedure_id: i32) -> Self {
pub fn new(start_procedure_id: ProcedureConfigID) -> Self {
Self {
procedure_mgr: ProcedureManager::new(start_procedure_id),
}
@ -25,7 +26,11 @@ impl NapGameMode for FreshGame {
Some(SceneInfo {
scene_type: self.scene_type() as u32,
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()
})

View file

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

View file

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

View file

@ -1,4 +1,5 @@
use super::{ItemUID, ResourceItem, Weapon};
use data::tables::WeaponID;
use proto::{ItemModelBin, ItemSync};
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();
self.weapons.push(Weapon::new(template_id, uid));

View file

@ -1,11 +1,11 @@
use super::ItemUID;
use data::tables::{self, WeaponTemplate};
use data::tables::WeaponID;
use proto::{WeaponBin, WeaponInfo};
pub struct Weapon {
pub uid: ItemUID,
pub template_id: u32,
pub template_id: WeaponID,
pub star: u32,
pub level: u32,
pub exp: u32,
@ -13,8 +13,8 @@ pub struct Weapon {
}
impl Weapon {
pub fn new(template_id: u32, uid: ItemUID) -> Self {
let template = Self::get_template(template_id).unwrap();
pub fn new(template_id: WeaponID, uid: ItemUID) -> Self {
let template = template_id.template();
Self {
template_id,
@ -28,7 +28,7 @@ impl Weapon {
pub fn from_bin(bin: WeaponBin) -> Self {
Self {
template_id: bin.template_id,
template_id: WeaponID::new_unchecked(bin.template_id),
uid: bin.uid.into(),
star: bin.star,
level: bin.level,
@ -39,7 +39,7 @@ impl Weapon {
pub fn to_bin(&self) -> WeaponBin {
WeaponBin {
template_id: self.template_id,
template_id: self.template_id.value(),
uid: self.uid.value(),
star: self.star,
level: self.level,
@ -50,7 +50,7 @@ impl Weapon {
pub fn to_client(&self) -> WeaponInfo {
WeaponInfo {
template_id: self.template_id,
template_id: self.template_id.value(),
uid: self.uid.value(),
star: self.star,
level: self.level,
@ -59,8 +59,4 @@ impl Weapon {
..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};
pub struct BasicDataModel {
@ -6,7 +7,7 @@ pub struct BasicDataModel {
pub profile_icon: u32,
pub nick_name: Option<String>,
pub frontend_avatar_id: i32,
pub beginner_procedure_id: i32,
pub beginner_procedure_id: Option<ProcedureConfigID>,
}
impl Default for BasicDataModel {
@ -17,7 +18,7 @@ impl Default for BasicDataModel {
profile_icon: 3200000,
nick_name: None,
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,
profile_icon: bin.profile_icon,
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() {
true => None,
false => Some(bin.nick_name),
@ -56,7 +60,10 @@ impl BasicDataModel {
profile_icon: self.profile_icon,
frontend_avatar_id: self.frontend_avatar_id,
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 data::tables::UnlockConfigID;
use proto::{LockModelBin, UnlockData};
#[derive(Default)]
pub struct LockModel {
unlock_list: BTreeSet<i32>,
unlock_list: BTreeSet<UnlockConfigID>,
}
impl LockModel {
pub fn from_bin(bin: LockModelBin) -> 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 {
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 {
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()
}
}
pub fn add_unlock(&mut self, id: i32) {
pub fn add_unlock(&mut self, id: UnlockConfigID) {
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)
}
}

View file

@ -1,3 +1,4 @@
use data::tables::SectionConfigID;
use proto::{MainCityModelBin, TransformBin};
use super::{math::Vector3f, time::MainCityTime};
@ -6,7 +7,7 @@ pub struct MainCityModel {
pub position: Vector3f,
pub rotation: Vector3f,
pub main_city_time: MainCityTime,
pub section_id: u32,
pub section_id: SectionConfigID,
}
impl MainCityModel {
@ -22,7 +23,7 @@ impl MainCityModel {
rotation: self.position.to_vec(),
}),
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),
rotation: Vector3f::from_vec(transform.rotation),
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(),
rotation: Vector3f::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 super::game::{FrontendGame, GameInstance, LogicError};
@ -90,7 +90,11 @@ impl Player {
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
.weapons
.iter()

View file

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

View file

@ -8,7 +8,7 @@ pub use plot_play::ProcedurePlotPlay;
pub use procedure_mgr::ProcedureManager;
pub use select_role::ProcedureSelectRole;
use data::tables::{self, ProcedureConfigTemplate};
use data::tables::{self, ProcedureConfigID, ProcedureConfigTemplate};
use num_enum::TryFromPrimitive;
use thiserror::Error;
@ -45,21 +45,25 @@ pub enum ProcedureError {
#[error("can't advance procedure because it's not finished")]
NotFinished,
#[error("trying to complete procedure: {0}, current procedure: {1}")]
InvalidProcedureId(i32, i32),
InvalidProcedureId(ProcedureConfigID, ProcedureConfigID),
#[error("current procedure is NULL!")]
ProcedureIsNull,
}
pub trait ProcedureBase {
fn id(&self) -> i32;
fn id(&self) -> ProcedureConfigID;
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()
.find(|tmpl| tmpl.procedure_id == self.id())
.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>;

View file

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

View file

@ -1,30 +1,34 @@
use data::tables;
use data::tables::{self, ProcedureConfigID};
use super::{Procedure, ProcedureAction, ProcedureError};
pub struct ProcedureManager {
cur_procedure_id: i32,
cur_procedure_id: Option<ProcedureConfigID>,
procedures: Vec<Procedure>,
}
impl ProcedureManager {
pub fn new(start_procedure_id: i32) -> Self {
pub fn new(start_procedure_id: ProcedureConfigID) -> Self {
Self {
cur_procedure_id: start_procedure_id,
cur_procedure_id: Some(start_procedure_id),
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)
.collect(),
}
}
pub fn try_complete_procedure(&mut self, procedure_id: i32) -> Result<(), ProcedureError> {
(self.cur_procedure_id == procedure_id)
.then_some(())
.ok_or(ProcedureError::InvalidProcedureId(
procedure_id,
self.cur_procedure_id,
))?;
pub fn try_complete_procedure(
&mut self,
procedure_id: ProcedureConfigID,
) -> Result<(), ProcedureError> {
let Some(cur_procedure_id) = self.cur_procedure_id else {
return Err(ProcedureError::ProcedureIsNull);
};
(cur_procedure_id == procedure_id).then_some(()).ok_or(
ProcedureError::InvalidProcedureId(procedure_id, cur_procedure_id),
)?;
let procedure = self
.procedures
@ -33,7 +37,7 @@ impl ProcedureManager {
.ok_or(ProcedureError::ProcedureIsNull)?;
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(())
} else {
Err(ProcedureError::NotFinished)
@ -41,26 +45,30 @@ impl ProcedureManager {
}
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
.procedures
.iter_mut()
.find(|proc| proc.base().id() == self.cur_procedure_id)
.find(|proc| proc.base().id() == cur_procedure_id)
.ok_or(ProcedureError::ProcedureIsNull)?;
let state = procedure.base_mut().on_action(action)?;
tracing::info!(
"procedure action {action:?} performed, state: {state:?}, procedure id: {}",
self.cur_procedure_id
cur_procedure_id
);
Ok(())
}
pub fn procedure_id(&self) -> i32 {
pub fn procedure_id(&self) -> Option<ProcedureConfigID> {
self.cur_procedure_id
}
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};
pub struct ProcedureSelectRole {
id: i32,
id: ProcedureConfigID,
state: ProcedureState,
}
impl ProcedureSelectRole {
pub fn new(id: i32) -> Self {
pub fn new(id: ProcedureConfigID) -> Self {
Self {
id,
state: ProcedureState::Init,
@ -15,7 +17,7 @@ impl ProcedureSelectRole {
}
impl ProcedureBase for ProcedureSelectRole {
fn id(&self) -> i32 {
fn id(&self) -> ProcedureConfigID {
self.id
}

View file

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

View file

@ -1,3 +1,4 @@
use data::tables::AvatarBaseID;
use proto::{AvatarSync, RoleModelBin};
use crate::logic::role::Avatar;
@ -11,7 +12,7 @@ impl Default for RoleModel {
Self {
avatar_list: Self::DEFAULT_AVATARS
.iter()
.map(|tmpl_id| Avatar::new(*tmpl_id))
.map(|tmpl_id| Avatar::new(AvatarBaseID::new_unchecked(*tmpl_id)))
.collect(),
}
}
@ -20,7 +21,7 @@ impl Default for RoleModel {
impl RoleModel {
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
.avatar_list
.iter()