1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-07-09 15:37:45 +00:00

Send your completed quests list

We aren't going to be adding quests ever or anytime soon, so all you
can do right now is force every quest to unlock with the aptly named
!completeallquests.
This commit is contained in:
Joshua Goins 2025-07-01 19:49:25 -04:00
parent 97682086f0
commit 927c093915
10 changed files with 84 additions and 14 deletions

View file

@ -116,6 +116,7 @@ These special debug commands start with `!` and are custom to Kawari.
* `!finishevent`: Forcefully finishes the current event, useful if the script has an error and you're stuck talking to something.
* `!item <name>`: Gives you an item matching by name.
* `!inspect`: Prints info about the player.
* `!completeallquests`: Completes every quest in the game, useful for accessing stuff gated behind quest completion.
### GM commands

View file

@ -229,6 +229,11 @@
"name": "InventoryActionAck",
"opcode": 483,
"size": 16
},
{
"name": "QuestCompleteList",
"opcode": 240,
"size": 760
}
],
"ClientZoneIpcType": [

View file

@ -45,3 +45,4 @@ registerCommand("ost", DBG_DIR.."OnScreenTest.lua")
registerCommand("permtest", DBG_DIR.."PermissionTest.lua")
registerCommand("setpos", DBG_DIR.."SetPos.lua")
registerCommand("unlock", DBG_DIR.."Unlock.lua")
registerCommand("completeallquests", DBG_DIR.."CompleteAllQuests.lua")

View file

@ -0,0 +1,6 @@
required_rank = GM_RANK_DEBUG
command_sender = "[completeallquests] "
function onCommand(args, player)
player:complete_all_quests()
end

View file

@ -317,6 +317,8 @@ async fn client_loop(
.await;
}
connection.send_quest_information().await;
let zone_id = connection.player_data.zone_id;
connection.change_zone(zone_id).await;

View file

@ -327,6 +327,13 @@ pub enum ServerZoneIpcData {
#[brw(pad_after = 26)]
unk2: u16,
},
#[br(pre_assert(*magic == ServerZoneIpcType::QuestCompleteList))]
QuestCompleteList {
// TODO: what is this? a bitmask probably?
#[br(count = 760)]
#[bw(pad_size_to = 760)]
unk1: Vec<u8>,
},
Unknown {
#[br(count = size - 32)]
unk: Vec<u8>,

View file

@ -75,3 +75,6 @@ pub const UNLOCK_BITMASK_SIZE: usize = 92;
/// The size of the aetheryte unlock bitmask.
// TODO: this can be automatically derived from game data
pub const AETHERYTE_UNLOCK_BITMASK_SIZE: usize = 30;
/// The size of the completed quest bitmask.
pub const COMPLETED_QUEST_BITMASK_SIZE: usize = 691;

View file

@ -9,7 +9,7 @@ use mlua::Function;
use tokio::net::TcpStream;
use crate::{
OBFUSCATION_ENABLED_MODE,
COMPLETED_QUEST_BITMASK_SIZE, OBFUSCATION_ENABLED_MODE,
common::{
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
},
@ -82,6 +82,7 @@ pub struct PlayerData {
pub unlocks: Vec<u8>,
pub aetherytes: Vec<u8>,
pub completed_quests: Vec<u8>,
}
/// Represents a single connection between an instance of the client and the world server
@ -777,6 +778,10 @@ impl ZoneConnection {
.add_in_next_free_slot(Item::new(1, *id));
self.send_inventory(false).await;
}
Task::CompleteAllQuests {} => {
self.player_data.completed_quests = vec![0xFF; COMPLETED_QUEST_BITMASK_SIZE];
self.send_quest_information().await;
}
}
}
player.queued_tasks.clear();
@ -1195,4 +1200,26 @@ impl ZoneConnection {
.unwrap();
self.player_data.classjob_exp[index as usize] = exp;
}
pub async fn send_quest_information(&mut self) {
// quest complete list
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::QuestCompleteList,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::QuestCompleteList {
unk1: self.player_data.completed_quests.clone(),
},
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc,
data: SegmentData::Ipc { data: ipc },
})
.await;
}
}
}

View file

@ -4,7 +4,7 @@ use rusqlite::Connection;
use serde::Deserialize;
use crate::{
AETHERYTE_UNLOCK_BITMASK_SIZE, UNLOCK_BITMASK_SIZE,
AETHERYTE_UNLOCK_BITMASK_SIZE, COMPLETED_QUEST_BITMASK_SIZE, UNLOCK_BITMASK_SIZE,
common::{
CustomizeData, GameData, Position,
workdefinitions::{CharaMake, ClientSelectData, RemakeMode},
@ -48,7 +48,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, classjob_id INTEGER, classjob_levels STRING, classjob_exp STRING, unlocks STRING, aetherytes 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, aetherytes STRING, completed_quests STRING);";
connection.execute(query, ()).unwrap();
}
@ -278,7 +278,7 @@ 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 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, aetherytes, completed_quests FROM character_data WHERE content_id = ?1")
.unwrap();
let (
pos_x,
@ -293,6 +293,7 @@ impl WorldDatabase {
classjob_exp,
unlocks,
aetherytes,
completed_quests,
): (
f32,
f32,
@ -306,6 +307,7 @@ impl WorldDatabase {
String,
String,
String,
String,
) = stmt
.query_row((content_id,), |row| {
Ok((
@ -321,6 +323,7 @@ impl WorldDatabase {
row.get(9)?,
row.get(10)?,
row.get(11)?,
row.get(12)?,
))
})
.unwrap();
@ -345,6 +348,7 @@ impl WorldDatabase {
classjob_exp: serde_json::from_str(&classjob_exp).unwrap(),
unlocks: serde_json::from_str(&unlocks).unwrap(),
aetherytes: serde_json::from_str(&aetherytes).unwrap(),
completed_quests: serde_json::from_str(&completed_quests).unwrap(),
..Default::default()
}
}
@ -354,7 +358,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, classjob_id=?7, classjob_levels=?8, classjob_exp=?9, unlocks=?10, aetherytes=?11 WHERE content_id = ?12")
.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")
.unwrap();
stmt.execute((
data.zone_id,
@ -368,6 +372,7 @@ impl WorldDatabase {
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();
@ -534,6 +539,9 @@ impl WorldDatabase {
// 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(
@ -545,7 +553,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);",
"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);",
(
content_id,
name,
@ -558,6 +566,7 @@ impl WorldDatabase {
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(),
),
)
.unwrap();

View file

@ -36,6 +36,7 @@ pub enum Task {
RemoveGil { amount: u32 },
UnlockOrchestrion { id: u16, on: bool },
AddItem { id: u32 },
CompleteAllQuests {},
}
#[derive(Default, Clone)]
@ -369,6 +370,10 @@ impl LuaPlayer {
fn add_item(&mut self, id: u32) {
self.queued_tasks.push(Task::AddItem { id });
}
fn complete_all_quests(&mut self) {
self.queued_tasks.push(Task::CompleteAllQuests {});
}
}
impl UserData for LuaPlayer {
@ -496,6 +501,10 @@ impl UserData for LuaPlayer {
this.add_item(id);
Ok(())
});
methods.add_method_mut("complete_all_quests", |_, this, _: ()| {
this.complete_all_quests();
Ok(())
});
}
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {