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:
parent
97682086f0
commit
927c093915
10 changed files with 84 additions and 14 deletions
1
USAGE.md
1
USAGE.md
|
@ -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.
|
* `!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.
|
* `!item <name>`: Gives you an item matching by name.
|
||||||
* `!inspect`: Prints info about the player.
|
* `!inspect`: Prints info about the player.
|
||||||
|
* `!completeallquests`: Completes every quest in the game, useful for accessing stuff gated behind quest completion.
|
||||||
|
|
||||||
### GM commands
|
### GM commands
|
||||||
|
|
||||||
|
|
|
@ -229,6 +229,11 @@
|
||||||
"name": "InventoryActionAck",
|
"name": "InventoryActionAck",
|
||||||
"opcode": 483,
|
"opcode": 483,
|
||||||
"size": 16
|
"size": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "QuestCompleteList",
|
||||||
|
"opcode": 240,
|
||||||
|
"size": 760
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ClientZoneIpcType": [
|
"ClientZoneIpcType": [
|
||||||
|
|
|
@ -37,11 +37,12 @@ registerGMCommand(GM_TERRITORY_INFO, GM_DIR.."TerritoryInfo.lua")
|
||||||
-- Debug commands
|
-- Debug commands
|
||||||
-- Please keep these in alphabetical order!
|
-- Please keep these in alphabetical order!
|
||||||
|
|
||||||
registerCommand("classjob", DBG_DIR.."ClassJob.lua")
|
registerCommand("classjob", DBG_DIR.."ClassJob.lua")
|
||||||
registerCommand("festival", DBG_DIR.."Festival.lua")
|
registerCommand("festival", DBG_DIR.."Festival.lua")
|
||||||
registerCommand("inspect", GM_DIR.."InspectPlayer.lua") -- TODO: remove this once we figure out the GMInspect IPC opcode
|
registerCommand("inspect", GM_DIR.."InspectPlayer.lua") -- TODO: remove this once we figure out the GMInspect IPC opcode
|
||||||
registerCommand("nudge", DBG_DIR.."Nudge.lua")
|
registerCommand("nudge", DBG_DIR.."Nudge.lua")
|
||||||
registerCommand("ost", DBG_DIR.."OnScreenTest.lua")
|
registerCommand("ost", DBG_DIR.."OnScreenTest.lua")
|
||||||
registerCommand("permtest", DBG_DIR.."PermissionTest.lua")
|
registerCommand("permtest", DBG_DIR.."PermissionTest.lua")
|
||||||
registerCommand("setpos", DBG_DIR.."SetPos.lua")
|
registerCommand("setpos", DBG_DIR.."SetPos.lua")
|
||||||
registerCommand("unlock", DBG_DIR.."Unlock.lua")
|
registerCommand("unlock", DBG_DIR.."Unlock.lua")
|
||||||
|
registerCommand("completeallquests", DBG_DIR.."CompleteAllQuests.lua")
|
||||||
|
|
6
resources/scripts/commands/debug/CompleteAllQuests.lua
Normal file
6
resources/scripts/commands/debug/CompleteAllQuests.lua
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
required_rank = GM_RANK_DEBUG
|
||||||
|
command_sender = "[completeallquests] "
|
||||||
|
|
||||||
|
function onCommand(args, player)
|
||||||
|
player:complete_all_quests()
|
||||||
|
end
|
|
@ -317,6 +317,8 @@ async fn client_loop(
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connection.send_quest_information().await;
|
||||||
|
|
||||||
let zone_id = connection.player_data.zone_id;
|
let zone_id = connection.player_data.zone_id;
|
||||||
connection.change_zone(zone_id).await;
|
connection.change_zone(zone_id).await;
|
||||||
|
|
||||||
|
|
|
@ -327,6 +327,13 @@ pub enum ServerZoneIpcData {
|
||||||
#[brw(pad_after = 26)]
|
#[brw(pad_after = 26)]
|
||||||
unk2: u16,
|
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 {
|
Unknown {
|
||||||
#[br(count = size - 32)]
|
#[br(count = size - 32)]
|
||||||
unk: Vec<u8>,
|
unk: Vec<u8>,
|
||||||
|
|
|
@ -75,3 +75,6 @@ pub const UNLOCK_BITMASK_SIZE: usize = 92;
|
||||||
/// The size of the aetheryte unlock bitmask.
|
/// The size of the aetheryte unlock bitmask.
|
||||||
// TODO: this can be automatically derived from game data
|
// TODO: this can be automatically derived from game data
|
||||||
pub const AETHERYTE_UNLOCK_BITMASK_SIZE: usize = 30;
|
pub const AETHERYTE_UNLOCK_BITMASK_SIZE: usize = 30;
|
||||||
|
|
||||||
|
/// The size of the completed quest bitmask.
|
||||||
|
pub const COMPLETED_QUEST_BITMASK_SIZE: usize = 691;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use mlua::Function;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
OBFUSCATION_ENABLED_MODE,
|
COMPLETED_QUEST_BITMASK_SIZE, OBFUSCATION_ENABLED_MODE,
|
||||||
common::{
|
common::{
|
||||||
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
|
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
|
||||||
},
|
},
|
||||||
|
@ -82,6 +82,7 @@ pub struct PlayerData {
|
||||||
|
|
||||||
pub unlocks: Vec<u8>,
|
pub unlocks: Vec<u8>,
|
||||||
pub aetherytes: 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
|
/// 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));
|
.add_in_next_free_slot(Item::new(1, *id));
|
||||||
self.send_inventory(false).await;
|
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();
|
player.queued_tasks.clear();
|
||||||
|
@ -1195,4 +1200,26 @@ impl ZoneConnection {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.player_data.classjob_exp[index as usize] = exp;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use rusqlite::Connection;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AETHERYTE_UNLOCK_BITMASK_SIZE, UNLOCK_BITMASK_SIZE,
|
AETHERYTE_UNLOCK_BITMASK_SIZE, COMPLETED_QUEST_BITMASK_SIZE, UNLOCK_BITMASK_SIZE,
|
||||||
common::{
|
common::{
|
||||||
CustomizeData, GameData, Position,
|
CustomizeData, GameData, Position,
|
||||||
workdefinitions::{CharaMake, ClientSelectData, RemakeMode},
|
workdefinitions::{CharaMake, ClientSelectData, RemakeMode},
|
||||||
|
@ -48,7 +48,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, 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();
|
connection.execute(query, ()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ impl WorldDatabase {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
stmt = connection
|
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();
|
.unwrap();
|
||||||
let (
|
let (
|
||||||
pos_x,
|
pos_x,
|
||||||
|
@ -293,6 +293,7 @@ impl WorldDatabase {
|
||||||
classjob_exp,
|
classjob_exp,
|
||||||
unlocks,
|
unlocks,
|
||||||
aetherytes,
|
aetherytes,
|
||||||
|
completed_quests,
|
||||||
): (
|
): (
|
||||||
f32,
|
f32,
|
||||||
f32,
|
f32,
|
||||||
|
@ -306,6 +307,7 @@ impl WorldDatabase {
|
||||||
String,
|
String,
|
||||||
String,
|
String,
|
||||||
String,
|
String,
|
||||||
|
String,
|
||||||
) = stmt
|
) = stmt
|
||||||
.query_row((content_id,), |row| {
|
.query_row((content_id,), |row| {
|
||||||
Ok((
|
Ok((
|
||||||
|
@ -321,6 +323,7 @@ impl WorldDatabase {
|
||||||
row.get(9)?,
|
row.get(9)?,
|
||||||
row.get(10)?,
|
row.get(10)?,
|
||||||
row.get(11)?,
|
row.get(11)?,
|
||||||
|
row.get(12)?,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -345,6 +348,7 @@ impl WorldDatabase {
|
||||||
classjob_exp: serde_json::from_str(&classjob_exp).unwrap(),
|
classjob_exp: serde_json::from_str(&classjob_exp).unwrap(),
|
||||||
unlocks: serde_json::from_str(&unlocks).unwrap(),
|
unlocks: serde_json::from_str(&unlocks).unwrap(),
|
||||||
aetherytes: serde_json::from_str(&aetherytes).unwrap(),
|
aetherytes: serde_json::from_str(&aetherytes).unwrap(),
|
||||||
|
completed_quests: serde_json::from_str(&completed_quests).unwrap(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,7 +358,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, 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();
|
.unwrap();
|
||||||
stmt.execute((
|
stmt.execute((
|
||||||
data.zone_id,
|
data.zone_id,
|
||||||
|
@ -368,6 +372,7 @@ impl WorldDatabase {
|
||||||
serde_json::to_string(&data.classjob_exp).unwrap(),
|
serde_json::to_string(&data.classjob_exp).unwrap(),
|
||||||
serde_json::to_string(&data.unlocks).unwrap(),
|
serde_json::to_string(&data.unlocks).unwrap(),
|
||||||
serde_json::to_string(&data.aetherytes).unwrap(),
|
serde_json::to_string(&data.aetherytes).unwrap(),
|
||||||
|
serde_json::to_string(&data.completed_quests).unwrap(),
|
||||||
data.content_id,
|
data.content_id,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -534,6 +539,9 @@ impl WorldDatabase {
|
||||||
// fill out initial aetherytes
|
// fill out initial aetherytes
|
||||||
let aetherytes = vec![0u8; AETHERYTE_UNLOCK_BITMASK_SIZE];
|
let aetherytes = vec![0u8; AETHERYTE_UNLOCK_BITMASK_SIZE];
|
||||||
|
|
||||||
|
// fill out initial completed quests`
|
||||||
|
let completed_quests = vec![0u8; COMPLETED_QUEST_BITMASK_SIZE];
|
||||||
|
|
||||||
// insert ids
|
// insert ids
|
||||||
connection
|
connection
|
||||||
.execute(
|
.execute(
|
||||||
|
@ -545,7 +553,7 @@ 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, ?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,
|
content_id,
|
||||||
name,
|
name,
|
||||||
|
@ -558,6 +566,7 @@ impl WorldDatabase {
|
||||||
serde_json::to_string(&classjob_exp).unwrap(),
|
serde_json::to_string(&classjob_exp).unwrap(),
|
||||||
serde_json::to_string(&unlocks).unwrap(),
|
serde_json::to_string(&unlocks).unwrap(),
|
||||||
serde_json::to_string(&aetherytes).unwrap(),
|
serde_json::to_string(&aetherytes).unwrap(),
|
||||||
|
serde_json::to_string(&completed_quests).unwrap(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -36,6 +36,7 @@ pub enum Task {
|
||||||
RemoveGil { amount: u32 },
|
RemoveGil { amount: u32 },
|
||||||
UnlockOrchestrion { id: u16, on: bool },
|
UnlockOrchestrion { id: u16, on: bool },
|
||||||
AddItem { id: u32 },
|
AddItem { id: u32 },
|
||||||
|
CompleteAllQuests {},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
|
@ -369,6 +370,10 @@ impl LuaPlayer {
|
||||||
fn add_item(&mut self, id: u32) {
|
fn add_item(&mut self, id: u32) {
|
||||||
self.queued_tasks.push(Task::AddItem { id });
|
self.queued_tasks.push(Task::AddItem { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn complete_all_quests(&mut self) {
|
||||||
|
self.queued_tasks.push(Task::CompleteAllQuests {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserData for LuaPlayer {
|
impl UserData for LuaPlayer {
|
||||||
|
@ -496,6 +501,10 @@ impl UserData for LuaPlayer {
|
||||||
this.add_item(id);
|
this.add_item(id);
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
methods.add_method_mut("complete_all_quests", |_, this, _: ()| {
|
||||||
|
this.complete_all_quests();
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue