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 {
GameMasterCommandType::SetLevel => {
connection.player_data.level = *arg0 as u8;
connection.player_data.set_current_level(*arg0 as i32);
connection.update_class_info().await;
}
GameMasterCommandType::ChangeWeather => {

View file

@ -46,7 +46,7 @@ pub struct PlayerData {
pub account_id: u32,
pub classjob_id: u8,
pub level: u8,
pub classjob_levels: [i32; 32],
pub curr_hp: u32,
pub max_hp: u32,
pub curr_mp: u16,
@ -64,6 +64,16 @@ pub struct PlayerData {
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
pub struct ZoneConnection {
pub config: WorldConfig,
@ -137,8 +147,6 @@ impl ZoneConnection {
pub async fn initialize(&mut self, actor_id: u32) {
// some still hardcoded values
self.player_data.classjob_id = 1;
self.player_data.level = 5;
self.player_data.curr_hp = 100;
self.player_data.max_hp = 100;
self.player_data.curr_mp = 10000;
@ -282,8 +290,8 @@ impl ZoneConnection {
data: ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo {
class_id: self.player_data.classjob_id as u16,
unknown: 1,
synced_level: self.player_data.level as u16,
class_level: self.player_data.level as u16,
synced_level: self.player_data.current_level() as u16,
class_level: self.player_data.current_level() as u16,
..Default::default()
}),
..Default::default()
@ -658,7 +666,7 @@ impl ZoneConnection {
data: ServerZoneIpcData::StatusEffectList(StatusEffectList {
statues: list,
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,
max_hp: self.player_data.max_hp,
curr_mp: self.player_data.curr_mp,
@ -779,7 +787,7 @@ impl ZoneConnection {
hp_max: self.player_data.max_hp,
mp_curr: self.player_data.curr_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),
look: chara_details.chara_make.customize,
display_flags: DisplayFlag::UNK,

View file

@ -47,7 +47,7 @@ impl WorldDatabase {
// 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();
}
@ -142,17 +142,19 @@ impl WorldDatabase {
.unwrap();
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();
let (pos_x, pos_y, pos_z, rotation, zone_id, inventory_json, gm_rank): (
f32,
f32,
f32,
f32,
u16,
String,
u8,
) = stmt
let (
pos_x,
pos_y,
pos_z,
rotation,
zone_id,
inventory_json,
gm_rank,
classjob_id,
classjob_levels,
): (f32, f32, f32, f32, u16, String, u8, i32, String) = stmt
.query_row((content_id,), |row| {
Ok((
row.get(0)?,
@ -162,6 +164,8 @@ impl WorldDatabase {
row.get(4)?,
row.get(5)?,
row.get(6)?,
row.get(7)?,
row.get(8)?,
))
})
.unwrap();
@ -181,6 +185,8 @@ impl WorldDatabase {
zone_id,
inventory,
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()
}
}
@ -190,7 +196,7 @@ impl WorldDatabase {
let connection = self.connection.lock().unwrap();
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();
stmt.execute((
data.zone_id,
@ -199,6 +205,8 @@ impl WorldDatabase {
data.position.z,
data.rotation,
serde_json::to_string(&data.inventory).unwrap(),
data.classjob_id,
serde_json::to_string(&data.classjob_levels).unwrap(),
data.content_id,
))
.unwrap();
@ -247,30 +255,41 @@ impl WorldDatabase {
for (index, (content_id, actor_id)) in content_actor_ids.iter().enumerate() {
let mut stmt = connection
.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();
let result: Result<(String, String, u16, String, i32), rusqlite::Error> = stmt
.query_row((content_id,), |row| {
let result: Result<(String, String, u16, String, i32, i32, String), rusqlite::Error> =
stmt.query_row((content_id,), |row| {
Ok((
row.get(0)?,
row.get(1)?,
row.get(2)?,
row.get(3)?,
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 inventory: Inventory = serde_json::from_str(&inventory_json).unwrap();
let select_data = ClientSelectData {
character_name: name.clone(),
current_class: 2,
class_levels: [5; 32],
current_class: classjob_id,
class_levels: serde_json::from_str(&classjob_levels).unwrap(),
race: chara_make.customize.race as i32,
subrace: chara_make.customize.subrace as i32,
gender: chara_make.customize.gender as i32,
@ -331,7 +350,7 @@ impl WorldDatabase {
&self,
service_account_id: u32,
name: &str,
chara_make: &str,
chara_make_str: &str,
city_state: u8,
zone_id: u16,
inventory: Inventory,
@ -341,6 +360,11 @@ impl WorldDatabase {
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
connection
.execute(
@ -352,14 +376,16 @@ impl WorldDatabase {
// insert char data
connection
.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,
name,
chara_make,
chara_make_str,
city_state,
zone_id,
serde_json::to_string(&inventory).unwrap(),
chara_make.classjob_id,
serde_json::to_string(&classjob_levels).unwrap(),
),
)
.unwrap();