1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-06-30 11:47:45 +00:00

Make the current classjob persistent, and your levels too

This requires another database wipe, or if you're savvy enough a few
schema edits.
This commit is contained in:
Joshua Goins 2025-06-20 19:08:53 -04:00
parent a24e0a5658
commit 2a1491c8f9
3 changed files with 63 additions and 29 deletions

View file

@ -587,7 +587,7 @@ async fn client_loop(
match &command { match &command {
GameMasterCommandType::SetLevel => { GameMasterCommandType::SetLevel => {
connection.player_data.level = *arg0 as u8; connection.player_data.set_current_level(*arg0 as i32);
connection.update_class_info().await; connection.update_class_info().await;
} }
GameMasterCommandType::ChangeWeather => { GameMasterCommandType::ChangeWeather => {

View file

@ -46,7 +46,7 @@ pub struct PlayerData {
pub account_id: u32, pub account_id: u32,
pub classjob_id: u8, pub classjob_id: u8,
pub level: u8, pub classjob_levels: [i32; 32],
pub curr_hp: u32, pub curr_hp: u32,
pub max_hp: u32, pub max_hp: u32,
pub curr_mp: u16, pub curr_mp: u16,
@ -64,6 +64,16 @@ pub struct PlayerData {
pub gm_invisible: bool, pub gm_invisible: bool,
} }
impl PlayerData {
pub fn current_level(&self) -> i32 {
self.classjob_levels[self.classjob_id as usize]
}
pub fn set_current_level(&mut self, level: i32) {
self.classjob_levels[self.classjob_id as usize] = level;
}
}
/// Represents a single connection between an instance of the client and the world server /// Represents a single connection between an instance of the client and the world server
pub struct ZoneConnection { pub struct ZoneConnection {
pub config: WorldConfig, pub config: WorldConfig,
@ -137,8 +147,6 @@ impl ZoneConnection {
pub async fn initialize(&mut self, actor_id: u32) { pub async fn initialize(&mut self, actor_id: u32) {
// some still hardcoded values // some still hardcoded values
self.player_data.classjob_id = 1;
self.player_data.level = 5;
self.player_data.curr_hp = 100; self.player_data.curr_hp = 100;
self.player_data.max_hp = 100; self.player_data.max_hp = 100;
self.player_data.curr_mp = 10000; self.player_data.curr_mp = 10000;
@ -282,8 +290,8 @@ impl ZoneConnection {
data: ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo { data: ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo {
class_id: self.player_data.classjob_id as u16, class_id: self.player_data.classjob_id as u16,
unknown: 1, unknown: 1,
synced_level: self.player_data.level as u16, synced_level: self.player_data.current_level() as u16,
class_level: self.player_data.level as u16, class_level: self.player_data.current_level() as u16,
..Default::default() ..Default::default()
}), }),
..Default::default() ..Default::default()
@ -658,7 +666,7 @@ impl ZoneConnection {
data: ServerZoneIpcData::StatusEffectList(StatusEffectList { data: ServerZoneIpcData::StatusEffectList(StatusEffectList {
statues: list, statues: list,
classjob_id: self.player_data.classjob_id, classjob_id: self.player_data.classjob_id,
level: self.player_data.level, level: self.player_data.current_level() as u8,
curr_hp: self.player_data.curr_hp, curr_hp: self.player_data.curr_hp,
max_hp: self.player_data.max_hp, max_hp: self.player_data.max_hp,
curr_mp: self.player_data.curr_mp, curr_mp: self.player_data.curr_mp,
@ -779,7 +787,7 @@ impl ZoneConnection {
hp_max: self.player_data.max_hp, hp_max: self.player_data.max_hp,
mp_curr: self.player_data.curr_mp, mp_curr: self.player_data.curr_mp,
mp_max: self.player_data.max_mp, mp_max: self.player_data.max_mp,
level: self.player_data.level, level: self.player_data.current_level() as u8,
object_kind: ObjectKind::Player(PlayerSubKind::Player), object_kind: ObjectKind::Player(PlayerSubKind::Player),
look: chara_details.chara_make.customize, look: chara_details.chara_make.customize,
display_flags: DisplayFlag::UNK, display_flags: DisplayFlag::UNK,

View file

@ -47,7 +47,7 @@ impl WorldDatabase {
// Create characters data table // Create characters data table
{ {
let query = "CREATE TABLE IF NOT EXISTS character_data (content_id INTEGER PRIMARY KEY, name STRING, chara_make STRING, city_state INTEGER, zone_id INTEGER, pos_x REAL, pos_y REAL, pos_z REAL, rotation REAL, inventory STRING, remake_mode INTEGER, gm_rank INTEGER);"; let query = "CREATE TABLE IF NOT EXISTS character_data (content_id INTEGER PRIMARY KEY, name STRING, chara_make STRING, city_state INTEGER, zone_id INTEGER, pos_x REAL, pos_y REAL, pos_z REAL, rotation REAL, inventory STRING, remake_mode INTEGER, gm_rank INTEGER, classjob_id INTEGER, classjob_levels STRING);";
connection.execute(query, ()).unwrap(); connection.execute(query, ()).unwrap();
} }
@ -142,17 +142,19 @@ impl WorldDatabase {
.unwrap(); .unwrap();
stmt = connection stmt = connection
.prepare("SELECT pos_x, pos_y, pos_z, rotation, zone_id, inventory, gm_rank FROM character_data WHERE content_id = ?1") .prepare("SELECT pos_x, pos_y, pos_z, rotation, zone_id, inventory, gm_rank, classjob_id, classjob_levels FROM character_data WHERE content_id = ?1")
.unwrap(); .unwrap();
let (pos_x, pos_y, pos_z, rotation, zone_id, inventory_json, gm_rank): ( let (
f32, pos_x,
f32, pos_y,
f32, pos_z,
f32, rotation,
u16, zone_id,
String, inventory_json,
u8, gm_rank,
) = stmt classjob_id,
classjob_levels,
): (f32, f32, f32, f32, u16, String, u8, i32, String) = stmt
.query_row((content_id,), |row| { .query_row((content_id,), |row| {
Ok(( Ok((
row.get(0)?, row.get(0)?,
@ -162,6 +164,8 @@ impl WorldDatabase {
row.get(4)?, row.get(4)?,
row.get(5)?, row.get(5)?,
row.get(6)?, row.get(6)?,
row.get(7)?,
row.get(8)?,
)) ))
}) })
.unwrap(); .unwrap();
@ -181,6 +185,8 @@ impl WorldDatabase {
zone_id, zone_id,
inventory, inventory,
gm_rank: GameMasterRank::try_from(gm_rank).unwrap(), gm_rank: GameMasterRank::try_from(gm_rank).unwrap(),
classjob_id: classjob_id as u8,
classjob_levels: serde_json::from_str(&classjob_levels).unwrap(),
..Default::default() ..Default::default()
} }
} }
@ -190,7 +196,7 @@ impl WorldDatabase {
let connection = self.connection.lock().unwrap(); let connection = self.connection.lock().unwrap();
let mut stmt = connection let mut stmt = connection
.prepare("UPDATE character_data SET zone_id=?1, pos_x=?2, pos_y=?3, pos_z=?4, rotation=?5, inventory=?6 WHERE content_id = ?7") .prepare("UPDATE character_data SET zone_id=?1, pos_x=?2, pos_y=?3, pos_z=?4, rotation=?5, inventory=?6, classjob_id=?7, classjob_levels=?8 WHERE content_id = ?9")
.unwrap(); .unwrap();
stmt.execute(( stmt.execute((
data.zone_id, data.zone_id,
@ -199,6 +205,8 @@ impl WorldDatabase {
data.position.z, data.position.z,
data.rotation, data.rotation,
serde_json::to_string(&data.inventory).unwrap(), serde_json::to_string(&data.inventory).unwrap(),
data.classjob_id,
serde_json::to_string(&data.classjob_levels).unwrap(),
data.content_id, data.content_id,
)) ))
.unwrap(); .unwrap();
@ -247,30 +255,41 @@ impl WorldDatabase {
for (index, (content_id, actor_id)) in content_actor_ids.iter().enumerate() { for (index, (content_id, actor_id)) in content_actor_ids.iter().enumerate() {
let mut stmt = connection let mut stmt = connection
.prepare( .prepare(
"SELECT name, chara_make, zone_id, inventory, remake_mode FROM character_data WHERE content_id = ?1", "SELECT name, chara_make, zone_id, inventory, remake_mode, classjob_id, classjob_levels FROM character_data WHERE content_id = ?1",
) )
.unwrap(); .unwrap();
let result: Result<(String, String, u16, String, i32), rusqlite::Error> = stmt let result: Result<(String, String, u16, String, i32, i32, String), rusqlite::Error> =
.query_row((content_id,), |row| { stmt.query_row((content_id,), |row| {
Ok(( Ok((
row.get(0)?, row.get(0)?,
row.get(1)?, row.get(1)?,
row.get(2)?, row.get(2)?,
row.get(3)?, row.get(3)?,
row.get(4)?, row.get(4)?,
row.get(5)?,
row.get(6)?,
)) ))
}); });
if let Ok((name, chara_make, zone_id, inventory_json, remake_mode)) = result { if let Ok((
name,
chara_make,
zone_id,
inventory_json,
remake_mode,
classjob_id,
classjob_levels,
)) = result
{
let chara_make = CharaMake::from_json(&chara_make); let chara_make = CharaMake::from_json(&chara_make);
let inventory: Inventory = serde_json::from_str(&inventory_json).unwrap(); let inventory: Inventory = serde_json::from_str(&inventory_json).unwrap();
let select_data = ClientSelectData { let select_data = ClientSelectData {
character_name: name.clone(), character_name: name.clone(),
current_class: 2, current_class: classjob_id,
class_levels: [5; 32], class_levels: serde_json::from_str(&classjob_levels).unwrap(),
race: chara_make.customize.race as i32, race: chara_make.customize.race as i32,
subrace: chara_make.customize.subrace as i32, subrace: chara_make.customize.subrace as i32,
gender: chara_make.customize.gender as i32, gender: chara_make.customize.gender as i32,
@ -331,7 +350,7 @@ impl WorldDatabase {
&self, &self,
service_account_id: u32, service_account_id: u32,
name: &str, name: &str,
chara_make: &str, chara_make_str: &str,
city_state: u8, city_state: u8,
zone_id: u16, zone_id: u16,
inventory: Inventory, inventory: Inventory,
@ -341,6 +360,11 @@ impl WorldDatabase {
let connection = self.connection.lock().unwrap(); let connection = self.connection.lock().unwrap();
// fill out the initial classjob
let chara_make = CharaMake::from_json(chara_make_str);
let mut classjob_levels = [0i32; 32];
classjob_levels[chara_make.classjob_id as usize] = 1; // inital level
// insert ids // insert ids
connection connection
.execute( .execute(
@ -352,14 +376,16 @@ impl WorldDatabase {
// insert char data // insert char data
connection connection
.execute( .execute(
"INSERT INTO character_data VALUES (?1, ?2, ?3, ?4, ?5, 0.0, 0.0, 0.0, 0.0, ?6, 0, 90);", "INSERT INTO character_data VALUES (?1, ?2, ?3, ?4, ?5, 0.0, 0.0, 0.0, 0.0, ?6, 0, 90, ?7, ?8);",
( (
content_id, content_id,
name, name,
chara_make, chara_make_str,
city_state, city_state,
zone_id, zone_id,
serde_json::to_string(&inventory).unwrap(), serde_json::to_string(&inventory).unwrap(),
chara_make.classjob_id,
serde_json::to_string(&classjob_levels).unwrap(),
), ),
) )
.unwrap(); .unwrap();