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

Support importing even more data from Auracite

This includes unlocked class jobs, levels, your inventory and unlock
flags.
This commit is contained in:
Joshua Goins 2025-06-28 13:55:11 -04:00
parent 72bff848b5
commit 8f61fc36bd
5 changed files with 151 additions and 7 deletions

View file

@ -312,6 +312,14 @@ impl GameData {
self.get_weather_rate(*weather_rate_id as u32)
}
/// Gets the array index used in EXP & levels.
pub fn get_exp_array_index(&mut self, classjob_id: u16) -> Option<i8> {
let sheet = ClassJobSheet::read_from(&mut self.game_data, Language::English)?;
let row = sheet.get_row(classjob_id as u32)?;
row.ExpArrayIndex().into_i8().copied()
}
}
// Simple enum for GameData::get_territory_name

View file

@ -94,7 +94,6 @@ pub struct PlayerStatus {
#[br(count = UNLOCK_BITMASK_SIZE)]
#[bw(pad_size_to = UNLOCK_BITMASK_SIZE)]
pub unlocks: Vec<u8>,
pub unknown10e: [u8; 28],
#[br(count = AETHERYTE_UNLOCK_BITMASK_SIZE)]
#[bw(pad_size_to = AETHERYTE_UNLOCK_BITMASK_SIZE)]
pub aetherytes: Vec<u8>,

View file

@ -70,7 +70,7 @@ pub fn get_supported_expac_versions() -> HashMap<&'static str, Version<'static>>
pub const OBFUSCATION_ENABLED_MODE: u8 = 41;
/// The size of the unlock bitmask.
pub const UNLOCK_BITMASK_SIZE: usize = 64;
pub const UNLOCK_BITMASK_SIZE: usize = 92;
/// The size of the aetheryte unlock bitmask.
// TODO: this can be automatically derived from game data

View file

@ -194,9 +194,11 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
service_account_id,
path,
} => {
let mut game_data = connection.gamedata.lock().unwrap();
connection
.database
.import_character(*service_account_id, path);
.import_character(&mut game_data, *service_account_id, path);
}
CustomIpcData::RemakeCharacter {
content_id,

View file

@ -9,7 +9,7 @@ use crate::{
CustomizeData, GameData, Position,
workdefinitions::{CharaMake, ClientSelectData, RemakeMode},
},
inventory::Inventory,
inventory::{Inventory, Item, Storage},
ipc::{
lobby::{CharacterDetails, CharacterFlag},
zone::GameMasterRank,
@ -57,7 +57,7 @@ impl WorldDatabase {
}
}
pub fn import_character(&self, service_account_id: u32, path: &str) {
pub fn import_character(&self, game_data: &mut GameData, service_account_id: u32, path: &str) {
tracing::info!("Importing character backup from {path}...");
let file = std::fs::File::open(path).unwrap();
@ -75,6 +75,27 @@ impl WorldDatabase {
month: i32,
}
#[derive(Deserialize)]
struct ClassJobLevelValue {
level: i32,
exp: Option<u32>,
value: i32,
}
#[derive(Deserialize)]
struct InventoryItem {
slot: i32,
quantity: u32,
condition: i32,
id: u32,
glamour_id: u32,
}
#[derive(Deserialize)]
struct InventoryContainer {
items: Vec<InventoryItem>,
}
#[derive(Deserialize)]
struct CharacterJson {
name: String,
@ -82,6 +103,30 @@ impl WorldDatabase {
nameday: NamedayValue,
guardian: GenericValue,
voice: i32,
classjob_levels: Vec<ClassJobLevelValue>,
inventory1: InventoryContainer,
inventory2: InventoryContainer,
inventory3: InventoryContainer,
inventory4: InventoryContainer,
equipped_items: InventoryContainer,
currency: InventoryContainer,
armory_off_hand: InventoryContainer,
armory_head: InventoryContainer,
armory_body: InventoryContainer,
armory_hands: InventoryContainer,
armory_legs: InventoryContainer,
armory_ear: InventoryContainer,
armory_neck: InventoryContainer,
armory_wrist: InventoryContainer,
armory_rings: InventoryContainer,
armory_soul_crystal: InventoryContainer,
armory_main_hand: InventoryContainer,
unlock_flags: Vec<u8>,
unlock_aetherytes: Vec<u8>,
}
let character: CharacterJson;
@ -117,8 +162,7 @@ impl WorldDatabase {
unk2: 1,
};
// TODO: import inventory
self.create_player_data(
let (_, actor_id) = self.create_player_data(
service_account_id,
&character.name,
&chara_make.to_json(),
@ -127,6 +171,97 @@ impl WorldDatabase {
Inventory::default(),
);
let mut player_data = self.find_player_data(actor_id);
// import jobs
for classjob in &character.classjob_levels {
// find the array index of the job
let index = game_data
.get_exp_array_index(classjob.value as u16)
.unwrap();
player_data.classjob_levels[index as usize] = classjob.level;
if let Some(exp) = classjob.exp {
player_data.classjob_exp[index as usize] = exp;
}
}
let process_inventory_container =
|container: &InventoryContainer, target: &mut dyn Storage| {
for item in &container.items {
if item.slot as u32 > target.max_slots() {
continue;
}
*target.get_slot_mut(item.slot as u16) = Item {
quantity: item.quantity,
id: item.id,
};
}
};
// import inventory
process_inventory_container(&character.inventory1, &mut player_data.inventory.pages[0]);
process_inventory_container(&character.inventory2, &mut player_data.inventory.pages[1]);
process_inventory_container(&character.inventory3, &mut player_data.inventory.pages[2]);
process_inventory_container(&character.inventory4, &mut player_data.inventory.pages[3]);
process_inventory_container(
&character.equipped_items,
&mut player_data.inventory.equipped,
);
process_inventory_container(&character.currency, &mut player_data.inventory.currency);
process_inventory_container(
&character.armory_off_hand,
&mut player_data.inventory.armoury_off_hand,
);
process_inventory_container(
&character.armory_head,
&mut player_data.inventory.armoury_head,
);
process_inventory_container(
&character.armory_body,
&mut player_data.inventory.armoury_body,
);
process_inventory_container(
&character.armory_hands,
&mut player_data.inventory.armoury_hands,
);
process_inventory_container(
&character.armory_legs,
&mut player_data.inventory.armoury_legs,
);
process_inventory_container(
&character.armory_ear,
&mut player_data.inventory.armoury_earring,
);
process_inventory_container(
&character.armory_neck,
&mut player_data.inventory.armoury_necklace,
);
process_inventory_container(
&character.armory_wrist,
&mut player_data.inventory.armoury_bracelet,
);
process_inventory_container(
&character.armory_rings,
&mut player_data.inventory.armoury_rings,
);
process_inventory_container(
&character.armory_soul_crystal,
&mut player_data.inventory.armoury_soul_crystal,
);
process_inventory_container(
&character.armory_main_hand,
&mut player_data.inventory.armoury_main_hand,
);
// import unlock flags
player_data.unlocks = character.unlock_flags;
player_data.aetherytes = character.unlock_aetherytes;
self.commit_player_data(&player_data);
tracing::info!("{} added to the world!", character.name);
}