Added FirstLoginConfig

This commit is contained in:
traffic95 2025-05-24 00:12:49 +02:00
parent aec5a50d3e
commit ad0d79acc1
7 changed files with 157 additions and 45 deletions

View file

@ -0,0 +1,23 @@
interknot_level = 60
control_avatar_id = 2011
control_guise_avatar_id = 1371
day_of_week = 5
start_main_quest = false
default_section_id = 1
[avatar]
unlock_all = true
unlock_id_list = [ 1011, 1081 ]
level = 60
rank = 6
unlocked_talent_num = 0
talent_switch = [ false, false, false, false, false, false ]
passive_skill_level = 7
skill_level_map = [ 12, 12, 12, 12, 7, 7, 12 ]
[weapon]
unlock_all = true
unlock_id_list = [ 13101 ]
level = 60
star = 5
refine_level = 1

View file

@ -43,3 +43,38 @@ pub struct GachaMaterialConfig {
pub id: u32, pub id: u32,
pub count: u32, pub count: u32,
} }
#[derive(Deserialize)]
pub struct FirstLoginConfig {
pub interknot_level: u32,
pub control_avatar_id: u32,
pub control_guise_avatar_id: u32,
pub day_of_week: u32,
pub start_main_quest: bool,
pub default_section_id: u32,
pub avatar: FirstLoginAvatarConfig,
pub weapon: FirstLoginWeaponConfig,
}
#[derive(Deserialize)]
pub struct FirstLoginAvatarConfig {
pub unlock_all: bool,
#[serde(default)]
pub unlock_id_list: Vec<u32>,
pub level: u32,
pub rank: u32,
pub unlocked_talent_num: u32,
pub talent_switch: Vec<bool>,
pub passive_skill_level: u32,
pub skill_level_map: Vec<u32>,
}
#[derive(Deserialize)]
pub struct FirstLoginWeaponConfig {
pub unlock_all: bool,
#[serde(default)]
pub unlock_id_list: Vec<u32>,
pub level: u32,
pub star: u32,
pub refine_level: u32,
}

View file

@ -10,11 +10,10 @@ use vivian_logic::{
use vivian_proto::server_only::{AvatarData, AvatarItemInfo}; use vivian_proto::server_only::{AvatarData, AvatarItemInfo};
use crate::{ use crate::{
logic::{ config::FirstLoginAvatarConfig, logic::{
property::{Property, PropertyHashMap}, property::{Property, PropertyHashMap},
sync::{LoginDataSyncComponent, PlayerSyncComponent, SyncType}, sync::{LoginDataSyncComponent, PlayerSyncComponent, SyncType},
}, }, resources::NapResources
resources::NapResources,
}; };
use super::{Model, Saveable}; use super::{Model, Saveable};
@ -28,16 +27,24 @@ pub struct AvatarModel {
impl AvatarModel { impl AvatarModel {
pub fn on_first_login(&mut self, res: &NapResources) { pub fn on_first_login(&mut self, res: &NapResources) {
const STARTING_AVATARS: &[u32] = &[1011, 1081]; let cfg = &res.gameplay.first_login.avatar;
STARTING_AVATARS if cfg.unlock_all {
.iter() res
.filter_map(|id| { .templates
res.templates .avatar_base_template_tb()
.avatar_base_template_tb() .for_each(|tmpl| self.unlock_avatar(&tmpl, None, Some(cfg)));
.find(|tmpl| tmpl.id() == *id) } else {
}) cfg
.for_each(|tmpl| self.unlock_avatar(&tmpl, None)); .unlock_id_list
.iter()
.filter_map(|id| {
res.templates
.avatar_base_template_tb()
.find(|tmpl| tmpl.id() == *id)
})
.for_each(|tmpl| self.unlock_avatar(&tmpl, None, Some(cfg)));
}
} }
pub fn send_add_avatar_notify(&self, listener: &mut dyn NotifyListener) { pub fn send_add_avatar_notify(&self, listener: &mut dyn NotifyListener) {
@ -96,6 +103,7 @@ impl AvatarModel {
&mut self, &mut self,
base_template: &AvatarBaseTemplate, base_template: &AvatarBaseTemplate,
perform_type: Option<vivian_proto::add_avatar_sc_notify::PerformType>, perform_type: Option<vivian_proto::add_avatar_sc_notify::PerformType>,
first_login_cfg: Option<&FirstLoginAvatarConfig>,
) { ) {
const AVATAR_BLACKLIST: &[u32] = &[]; const AVATAR_BLACKLIST: &[u32] = &[];
@ -105,20 +113,28 @@ impl AvatarModel {
&& !self.avatar_map.contains_key(&avatar_id) && !self.avatar_map.contains_key(&avatar_id)
&& !AVATAR_BLACKLIST.contains(&avatar_id) && !AVATAR_BLACKLIST.contains(&avatar_id)
{ {
let mut skill_level_map: HashMap<EAvatarSkillType, u32> = (0..EAvatarSkillType::EnumCount.into())
.map(|ty| EAvatarSkillType::try_from(ty).unwrap())
.zip([0].into_iter().cycle())
.collect();
if let Some(cfg) = first_login_cfg {
skill_level_map = (0..EAvatarSkillType::EnumCount.into())
.map(|ty| EAvatarSkillType::try_from(ty).unwrap())
.zip(cfg.skill_level_map.clone())
.collect();
}
self.avatar_map.insert( self.avatar_map.insert(
avatar_id, avatar_id,
AvatarItem { AvatarItem {
id: avatar_id, id: avatar_id,
level: 1, level: first_login_cfg.map_or(1, |cfg| cfg.level),
exp: 0, exp: 0,
rank: 1, rank: first_login_cfg.map_or(1, |cfg| cfg.rank),
unlocked_talent_num: 0, unlocked_talent_num: first_login_cfg.map_or(0, |cfg| cfg.unlocked_talent_num),
talent_switch: [false; 6], talent_switch: first_login_cfg.map_or([false; 6], |cfg| cfg.talent_switch.as_slice().try_into().unwrap()),
passive_skill_level: 0, passive_skill_level: first_login_cfg.map_or(0, |cfg| cfg.passive_skill_level),
skill_level_map: (0..EAvatarSkillType::EnumCount.into()) skill_level_map,
.map(|ty| EAvatarSkillType::try_from(ty).unwrap())
.zip([0].into_iter().cycle())
.collect(),
weapon_uid: 0, weapon_uid: 0,
dressed_equip_map: HashMap::new(), dressed_equip_map: HashMap::new(),
first_get_time: time_util::unix_timestamp_seconds(), first_get_time: time_util::unix_timestamp_seconds(),

View file

@ -37,21 +37,47 @@ impl ItemModel {
.avatar_skin_base_template_tb() .avatar_skin_base_template_tb()
.for_each(|tmpl| self.item_count_map.insert(tmpl.id(), 1)); .for_each(|tmpl| self.item_count_map.insert(tmpl.id(), 1));
// Unlock all weapons as well let weapon_cfg = &res.gameplay.first_login.weapon;
res.templates.weapon_template_tb().for_each(|tmpl| {
let uid = self.next_uid(); if weapon_cfg.unlock_all {
self.weapon_map.insert( res.templates.weapon_template_tb().for_each(|tmpl| {
uid, let uid = self.next_uid();
WeaponItem { self.weapon_map.insert(
id: tmpl.item_id(), uid,
level: 60, WeaponItem {
exp: 0, id: tmpl.item_id(),
star: tmpl.star_limit() + 1, level: weapon_cfg.level,
refine_level: tmpl.refine_limit(), exp: 0,
lock: false, star: weapon_cfg.star,
}, refine_level: weapon_cfg.refine_level,
); lock: false,
}); },
);
});
} else {
weapon_cfg
.unlock_id_list
.iter()
.filter_map(|id| {
res.templates
.weapon_template_tb()
.find(|tmpl| tmpl.item_id() == *id)
})
.for_each(|tmpl| {
let uid = self.next_uid();
self.weapon_map.insert(
uid,
WeaponItem {
id: tmpl.item_id(),
level: weapon_cfg.level,
exp: 0,
star: weapon_cfg.star,
refine_level: weapon_cfg.refine_level,
lock: false,
},
);
});
}
} }
pub fn add_weapon(&mut self, template: &WeaponTemplate) -> u32 { pub fn add_weapon(&mut self, template: &WeaponTemplate) -> u32 {

View file

@ -140,21 +140,28 @@ impl Player {
// and it won't be needed to set avatar ids in basic module // and it won't be needed to set avatar ids in basic module
// (player will select them in beginner procedure) // (player will select them in beginner procedure)
let cfg = &self.resources.gameplay.first_login;
self.avatar_model.on_first_login(self.resources); self.avatar_model.on_first_login(self.resources);
self.basic_model.level.set(1); self.basic_model.level.set(cfg.interknot_level);
self.basic_model.avatar_id.set(2011); self.basic_model.avatar_id.set(cfg.control_avatar_id);
self.basic_model.control_avatar_id.set(2011); self.basic_model.control_avatar_id.set(cfg.control_avatar_id);
self.basic_model.control_guise_avatar_id.set(2011); self.basic_model.control_guise_avatar_id.set(cfg.control_guise_avatar_id);
self.main_city_model.day_of_week.set(5); // Friday self.main_city_model.day_of_week.set(cfg.day_of_week);
self.item_model.on_first_login(self.resources); self.item_model.on_first_login(self.resources);
self.misc_model.on_first_login(self.resources); self.misc_model.on_first_login(self.resources);
self.gacha_model.on_first_login(); self.gacha_model.on_first_login();
let mut main_city_quest_id = 10020001;
if !cfg.start_main_quest {
main_city_quest_id = 10020028;
}
self.quest_model self.quest_model
.add_main_city_quest(10020001, self.resources); .add_main_city_quest(main_city_quest_id, self.resources);
// Initialize hall scene with WorkShop section // Initialize hall scene with WorkShop section
let scene_uid = self.scene_model.next_scene_uid(); let scene_uid = self.scene_model.next_scene_uid();
@ -163,7 +170,7 @@ impl Player {
scene::SceneSnapshot { scene::SceneSnapshot {
scene_id: 1, scene_id: 1,
ext: scene::SceneSnapshotExt::Hall(scene::HallSceneSnapshot { ext: scene::SceneSnapshotExt::Hall(scene::HallSceneSnapshot {
cur_section_id: 2, cur_section_id: cfg.default_section_id,
sections: HashMap::new(), sections: HashMap::new(),
main_city_objects_state: HashMap::new(), main_city_objects_state: HashMap::new(),
}), }),
@ -234,7 +241,7 @@ impl Player {
AddItemSource::Mail => Some(PerformType::PerformAnimation), AddItemSource::Mail => Some(PerformType::PerformAnimation),
}; };
self.avatar_model.unlock_avatar(&template, perform_type); self.avatar_model.unlock_avatar(&template, perform_type, None);
} }
None None
} }

View file

@ -47,6 +47,10 @@ async fn main() -> Result<(), StartupError> {
concatcp!(CONFIG_DIR, "gacha_schedule.toml"), concatcp!(CONFIG_DIR, "gacha_schedule.toml"),
include_str!("../gacha_schedule.default.toml"), include_str!("../gacha_schedule.default.toml"),
), ),
first_login: common::config_util::load_or_create(
concatcp!(CONFIG_DIR, "first_login.toml"),
include_str!("../first_login.default.toml"),
),
}; };
let resources = NapResources::load(&config.resources, gameplay_cfg)?; let resources = NapResources::load(&config.resources, gameplay_cfg)?;

View file

@ -5,7 +5,7 @@ use config::{
LoadConditionsError, TemplateCollection, TemplateCollectionError, LoadConditionsError, TemplateCollection, TemplateCollectionError,
}; };
use crate::config::{GachaScheduleConfig, ResourceConfig}; use crate::config::{FirstLoginConfig, GachaScheduleConfig, ResourceConfig};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum LoadResourcesError { pub enum LoadResourcesError {
@ -40,6 +40,7 @@ pub struct NapResources {
pub struct ServerGameplayConfig { pub struct ServerGameplayConfig {
pub gacha_schedule: GachaScheduleConfig, pub gacha_schedule: GachaScheduleConfig,
pub first_login: FirstLoginConfig,
} }
impl NapResources { impl NapResources {