mirror of
https://github.com/redstrate/Kawari.git
synced 2025-07-23 21:17:45 +00:00
Store various content unlock & clear flags in database
I ended up sticking all of the unlock-relate information in a JSON object, instead of putting in more columns.
This commit is contained in:
parent
7d3deb8685
commit
234e804953
4 changed files with 157 additions and 52 deletions
|
@ -315,8 +315,18 @@ async fn client_loop(
|
|||
current_class: current_class as u8,
|
||||
current_job: connection.player_data.classjob_id,
|
||||
levels: connection.player_data.classjob_levels.map(|x| x as u16),
|
||||
unlocks: connection.player_data.unlocks.clone(),
|
||||
aetherytes: connection.player_data.aetherytes.clone(),
|
||||
unlocks: connection.player_data.unlocks.unlocks.clone(),
|
||||
aetherytes: connection.player_data.unlocks.aetherytes.clone(),
|
||||
unlocked_raids: connection.player_data.unlocks.unlocked_raids.clone().into(),
|
||||
unlocked_dungeons: connection.player_data.unlocks.unlocked_dungeons.clone(),
|
||||
unlocked_guildhests: connection.player_data.unlocks.unlocked_guildhests.clone(),
|
||||
unlocked_trials: connection.player_data.unlocks.unlocked_trials.clone(),
|
||||
unlocked_pvp: connection.player_data.unlocks.unlocked_pvp.clone(),
|
||||
cleared_raids: connection.player_data.unlocks.cleared_raids.clone(),
|
||||
cleared_dungeons: connection.player_data.unlocks.cleared_dungeons.clone(),
|
||||
cleared_guildhests: connection.player_data.unlocks.cleared_guildhests.clone(),
|
||||
cleared_trials: connection.player_data.unlocks.cleared_trials.clone(),
|
||||
cleared_pvp: connection.player_data.unlocks.cleared_pvp.clone(),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
|
|
|
@ -156,16 +156,46 @@ pub struct PlayerStatus {
|
|||
#[br(count = 141)]
|
||||
#[bw(pad_size_to = 141)]
|
||||
pub unknown948: Vec<u8>,
|
||||
pub unlocked_raids: [u8; RAID_ARRAY_SIZE],
|
||||
pub unlocked_dungeons: [u8; DUNGEON_ARRAY_SIZE],
|
||||
pub unlocked_guildhests: [u8; GUILDHEST_ARRAY_SIZE],
|
||||
pub unlocked_trials: [u8; TRIAL_ARRAY_SIZE],
|
||||
pub unlocked_pvp: [u8; PVP_ARRAY_SIZE],
|
||||
pub cleared_raids: [u8; RAID_ARRAY_SIZE],
|
||||
pub cleared_dungeons: [u8; DUNGEON_ARRAY_SIZE],
|
||||
pub cleared_guildhests: [u8; GUILDHEST_ARRAY_SIZE],
|
||||
pub cleared_trials: [u8; TRIAL_ARRAY_SIZE],
|
||||
pub cleared_pvp: [u8; PVP_ARRAY_SIZE],
|
||||
|
||||
#[br(count = RAID_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = RAID_ARRAY_SIZE)]
|
||||
pub unlocked_raids: Vec<u8>,
|
||||
|
||||
#[br(count = DUNGEON_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = DUNGEON_ARRAY_SIZE)]
|
||||
pub unlocked_dungeons: Vec<u8>,
|
||||
|
||||
#[br(count = GUILDHEST_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = GUILDHEST_ARRAY_SIZE)]
|
||||
pub unlocked_guildhests: Vec<u8>,
|
||||
|
||||
#[br(count = TRIAL_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = TRIAL_ARRAY_SIZE)]
|
||||
pub unlocked_trials: Vec<u8>,
|
||||
|
||||
#[br(count = PVP_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = PVP_ARRAY_SIZE)]
|
||||
pub unlocked_pvp: Vec<u8>,
|
||||
|
||||
#[br(count = RAID_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = RAID_ARRAY_SIZE)]
|
||||
pub cleared_raids: Vec<u8>,
|
||||
|
||||
#[br(count = DUNGEON_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = DUNGEON_ARRAY_SIZE)]
|
||||
pub cleared_dungeons: Vec<u8>,
|
||||
|
||||
#[br(count = GUILDHEST_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = GUILDHEST_ARRAY_SIZE)]
|
||||
pub cleared_guildhests: Vec<u8>,
|
||||
|
||||
#[br(count = TRIAL_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = TRIAL_ARRAY_SIZE)]
|
||||
pub cleared_trials: Vec<u8>,
|
||||
|
||||
#[br(count = PVP_ARRAY_SIZE)]
|
||||
#[bw(pad_size_to = PVP_ARRAY_SIZE)]
|
||||
pub cleared_pvp: Vec<u8>,
|
||||
|
||||
#[br(count = 16)]
|
||||
#[bw(pad_size_to = 16)]
|
||||
|
|
|
@ -6,11 +6,14 @@ use std::{
|
|||
};
|
||||
|
||||
use mlua::Function;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::{
|
||||
CLASSJOB_ARRAY_SIZE, COMPLETED_LEVEQUEST_BITMASK_SIZE, COMPLETED_QUEST_BITMASK_SIZE,
|
||||
ERR_INVENTORY_ADD_FAILED, LogMessageType,
|
||||
AETHERYTE_UNLOCK_BITMASK_SIZE, CLASSJOB_ARRAY_SIZE, COMPLETED_LEVEQUEST_BITMASK_SIZE,
|
||||
COMPLETED_QUEST_BITMASK_SIZE, DUNGEON_ARRAY_SIZE, ERR_INVENTORY_ADD_FAILED,
|
||||
GUILDHEST_ARRAY_SIZE, LogMessageType, PVP_ARRAY_SIZE, RAID_ARRAY_SIZE, TRIAL_ARRAY_SIZE,
|
||||
UNLOCK_BITMASK_SIZE,
|
||||
common::{
|
||||
GameData, INVALID_OBJECT_ID, ItemInfoQuery, ObjectId, ObjectTypeId, Position,
|
||||
timestamp_secs, value_to_flag_byte_index_value,
|
||||
|
@ -57,6 +60,43 @@ pub struct TeleportQuery {
|
|||
pub aetheryte_id: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UnlockData {
|
||||
pub unlocks: Vec<u8>,
|
||||
pub aetherytes: Vec<u8>,
|
||||
pub completed_quests: Vec<u8>,
|
||||
pub unlocked_raids: Vec<u8>,
|
||||
pub unlocked_dungeons: Vec<u8>,
|
||||
pub unlocked_guildhests: Vec<u8>,
|
||||
pub unlocked_trials: Vec<u8>,
|
||||
pub unlocked_pvp: Vec<u8>,
|
||||
pub cleared_raids: Vec<u8>,
|
||||
pub cleared_dungeons: Vec<u8>,
|
||||
pub cleared_guildhests: Vec<u8>,
|
||||
pub cleared_trials: Vec<u8>,
|
||||
pub cleared_pvp: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for UnlockData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
unlocks: vec![0x0; UNLOCK_BITMASK_SIZE],
|
||||
aetherytes: vec![0x0; AETHERYTE_UNLOCK_BITMASK_SIZE],
|
||||
completed_quests: vec![0x0; COMPLETED_QUEST_BITMASK_SIZE],
|
||||
unlocked_raids: vec![0x0; RAID_ARRAY_SIZE],
|
||||
unlocked_dungeons: vec![0x0; DUNGEON_ARRAY_SIZE],
|
||||
unlocked_guildhests: vec![0x0; GUILDHEST_ARRAY_SIZE],
|
||||
unlocked_trials: vec![0x0; TRIAL_ARRAY_SIZE],
|
||||
unlocked_pvp: vec![0x0; PVP_ARRAY_SIZE],
|
||||
cleared_raids: vec![0x0; RAID_ARRAY_SIZE],
|
||||
cleared_dungeons: vec![0x0; DUNGEON_ARRAY_SIZE],
|
||||
cleared_guildhests: vec![0x0; GUILDHEST_ARRAY_SIZE],
|
||||
cleared_trials: vec![0x0; TRIAL_ARRAY_SIZE],
|
||||
cleared_pvp: vec![0x0; PVP_ARRAY_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PlayerData {
|
||||
// Static data
|
||||
|
@ -83,15 +123,13 @@ pub struct PlayerData {
|
|||
pub gm_rank: GameMasterRank,
|
||||
pub gm_invisible: bool,
|
||||
|
||||
pub unlocks: Vec<u8>,
|
||||
pub aetherytes: Vec<u8>,
|
||||
pub completed_quests: Vec<u8>,
|
||||
pub item_sequence: u32,
|
||||
pub shop_sequence: u32,
|
||||
/// Store the target actor id for the purpose of chaining cutscenes.
|
||||
pub target_actorid: ObjectTypeId,
|
||||
/// The server-side copy of NPC shop buyback lists.
|
||||
pub buyback_list: BuyBackList,
|
||||
pub unlocks: UnlockData,
|
||||
}
|
||||
|
||||
/// Various obsfucation-related bits like the seeds and keys for this connection.
|
||||
|
@ -835,7 +873,7 @@ impl ZoneConnection {
|
|||
}
|
||||
Task::Unlock { id } => {
|
||||
let (value, index) = value_to_flag_byte_index_value(*id);
|
||||
self.player_data.unlocks[index as usize] |= value;
|
||||
self.player_data.unlocks.unlocks[index as usize] |= value;
|
||||
|
||||
self.actor_control_self(ActorControlSelf {
|
||||
category: ActorControlCategory::ToggleUnlock {
|
||||
|
@ -851,9 +889,9 @@ impl ZoneConnection {
|
|||
for i in 1..239 {
|
||||
let (value, index) = value_to_flag_byte_index_value(i);
|
||||
if *on {
|
||||
self.player_data.aetherytes[index as usize] |= value;
|
||||
self.player_data.unlocks.aetherytes[index as usize] |= value;
|
||||
} else {
|
||||
self.player_data.aetherytes[index as usize] ^= value;
|
||||
self.player_data.unlocks.aetherytes[index as usize] ^= value;
|
||||
}
|
||||
|
||||
/* Unknown if this will make the server panic from a flood of packets.
|
||||
|
@ -869,9 +907,9 @@ impl ZoneConnection {
|
|||
} else {
|
||||
let (value, index) = value_to_flag_byte_index_value(*id);
|
||||
if *on {
|
||||
self.player_data.aetherytes[index as usize] |= value;
|
||||
self.player_data.unlocks.aetherytes[index as usize] |= value;
|
||||
} else {
|
||||
self.player_data.aetherytes[index as usize] ^= value;
|
||||
self.player_data.unlocks.aetherytes[index as usize] ^= value;
|
||||
}
|
||||
|
||||
self.actor_control_self(ActorControlSelf {
|
||||
|
@ -955,7 +993,8 @@ impl ZoneConnection {
|
|||
}
|
||||
}
|
||||
Task::CompleteAllQuests {} => {
|
||||
self.player_data.completed_quests = vec![0xFF; COMPLETED_QUEST_BITMASK_SIZE];
|
||||
self.player_data.unlocks.completed_quests =
|
||||
vec![0xFF; COMPLETED_QUEST_BITMASK_SIZE];
|
||||
self.send_quest_information().await;
|
||||
}
|
||||
Task::UnlockContent { id } => {
|
||||
|
@ -1637,7 +1676,7 @@ impl ZoneConnection {
|
|||
op_code: ServerZoneIpcType::QuestCompleteList,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::QuestCompleteList {
|
||||
completed_quests: self.player_data.completed_quests.clone(),
|
||||
completed_quests: self.player_data.unlocks.completed_quests.clone(),
|
||||
unk2: vec![0xFF; 69],
|
||||
},
|
||||
..Default::default()
|
||||
|
|
|
@ -4,8 +4,7 @@ use rusqlite::Connection;
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
AETHERYTE_UNLOCK_BITMASK_SIZE, CLASSJOB_ARRAY_SIZE, COMPLETED_QUEST_BITMASK_SIZE,
|
||||
UNLOCK_BITMASK_SIZE,
|
||||
CLASSJOB_ARRAY_SIZE,
|
||||
common::{
|
||||
CustomizeData, GameData, ItemInfoQuery, Position,
|
||||
workdefinitions::{CharaMake, ClientSelectData, RemakeMode},
|
||||
|
@ -14,7 +13,7 @@ use crate::{
|
|||
ipc::lobby::{CharacterDetails, CharacterFlag},
|
||||
};
|
||||
|
||||
use super::PlayerData;
|
||||
use super::{PlayerData, connection::UnlockData};
|
||||
|
||||
pub struct WorldDatabase {
|
||||
connection: Mutex<Connection>,
|
||||
|
@ -50,7 +49,23 @@ 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, classjob_id INTEGER, classjob_levels STRING, classjob_exp STRING, unlocks STRING, aetherytes STRING, completed_quests STRING);";
|
||||
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,
|
||||
classjob_exp STRING,
|
||||
unlocks STRING);";
|
||||
connection.execute(query, ()).unwrap();
|
||||
}
|
||||
|
||||
|
@ -271,8 +286,8 @@ impl WorldDatabase {
|
|||
);
|
||||
|
||||
// import unlock flags
|
||||
player_data.unlocks = character.unlock_flags;
|
||||
player_data.aetherytes = character.unlock_aetherytes;
|
||||
player_data.unlocks.unlocks = character.unlock_flags;
|
||||
player_data.unlocks.aetherytes = character.unlock_aetherytes;
|
||||
|
||||
self.commit_player_data(&player_data);
|
||||
|
||||
|
@ -290,7 +305,20 @@ impl WorldDatabase {
|
|||
.unwrap();
|
||||
|
||||
stmt = connection
|
||||
.prepare("SELECT pos_x, pos_y, pos_z, rotation, zone_id, inventory, gm_rank, classjob_id, classjob_levels, classjob_exp, unlocks, aetherytes, completed_quests 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,
|
||||
classjob_exp,
|
||||
unlocks
|
||||
FROM character_data WHERE content_id = ?1",
|
||||
)
|
||||
.unwrap();
|
||||
let mut player_data: PlayerData = stmt
|
||||
.query_row((content_id,), |row| {
|
||||
|
@ -310,9 +338,7 @@ impl WorldDatabase {
|
|||
classjob_id: row.get(7)?,
|
||||
classjob_levels: json_unpack::<[i32; CLASSJOB_ARRAY_SIZE]>(row.get(8)?),
|
||||
classjob_exp: json_unpack::<[u32; CLASSJOB_ARRAY_SIZE]>(row.get(9)?),
|
||||
unlocks: json_unpack::<Vec<u8>>(row.get(10)?),
|
||||
aetherytes: json_unpack::<Vec<u8>>(row.get(11)?),
|
||||
completed_quests: json_unpack::<Vec<u8>>(row.get(12)?),
|
||||
unlocks: json_unpack(row.get(10)?),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
|
@ -367,9 +393,22 @@ 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, classjob_id=?7, classjob_levels=?8, classjob_exp=?9, unlocks=?10, aetherytes=?11, completed_quests=?12 WHERE content_id = ?13")
|
||||
.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,
|
||||
classjob_exp=?9,
|
||||
unlocks=?10
|
||||
WHERE content_id = ?11",
|
||||
)
|
||||
.unwrap();
|
||||
stmt.execute((
|
||||
stmt.execute(rusqlite::params![
|
||||
data.zone_id,
|
||||
data.position.x,
|
||||
data.position.y,
|
||||
|
@ -380,10 +419,8 @@ impl WorldDatabase {
|
|||
serde_json::to_string(&data.classjob_levels).unwrap(),
|
||||
serde_json::to_string(&data.classjob_exp).unwrap(),
|
||||
serde_json::to_string(&data.unlocks).unwrap(),
|
||||
serde_json::to_string(&data.aetherytes).unwrap(),
|
||||
serde_json::to_string(&data.completed_quests).unwrap(),
|
||||
data.content_id,
|
||||
))
|
||||
])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
@ -547,15 +584,6 @@ impl WorldDatabase {
|
|||
|
||||
let classjob_exp = [0u32; CLASSJOB_ARRAY_SIZE];
|
||||
|
||||
// fill out initial unlocks
|
||||
let unlocks = vec![0u8; UNLOCK_BITMASK_SIZE];
|
||||
|
||||
// fill out initial aetherytes
|
||||
let aetherytes = vec![0u8; AETHERYTE_UNLOCK_BITMASK_SIZE];
|
||||
|
||||
// fill out initial completed quests`
|
||||
let completed_quests = vec![0u8; COMPLETED_QUEST_BITMASK_SIZE];
|
||||
|
||||
// insert ids
|
||||
connection
|
||||
.execute(
|
||||
|
@ -567,7 +595,7 @@ 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, ?7, ?8, ?9, ?10, ?11, ?12);",
|
||||
"INSERT INTO character_data VALUES (?1, ?2, ?3, ?4, ?5, 0.0, 0.0, 0.0, 0.0, ?6, 0, 90, ?7, ?8, ?9, ?10);",
|
||||
(
|
||||
content_id,
|
||||
name,
|
||||
|
@ -578,9 +606,7 @@ impl WorldDatabase {
|
|||
chara_make.classjob_id,
|
||||
serde_json::to_string(&classjob_levels).unwrap(),
|
||||
serde_json::to_string(&classjob_exp).unwrap(),
|
||||
serde_json::to_string(&unlocks).unwrap(),
|
||||
serde_json::to_string(&aetherytes).unwrap(),
|
||||
serde_json::to_string(&completed_quests).unwrap(),
|
||||
serde_json::to_string(&UnlockData::default()).unwrap(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
|
Loading…
Add table
Reference in a new issue