add a way for joinable quests to lock themselves
This commit is contained in:
+2
-2
@@ -577,7 +577,7 @@ static void server_command_qsync_qsyncall(shared_ptr<Client> c, const std::strin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
G_SyncQuestData_6x77 cmd;
|
G_SyncQuestRegister_6x77 cmd;
|
||||||
cmd.header = {0x77, 0x03, 0x0000};
|
cmd.header = {0x77, 0x03, 0x0000};
|
||||||
cmd.register_number = stoul(tokens[0].substr(1), nullptr, 0);
|
cmd.register_number = stoul(tokens[0].substr(1), nullptr, 0);
|
||||||
cmd.unused = 0;
|
cmd.unused = 0;
|
||||||
@@ -616,7 +616,7 @@ static void proxy_command_qsync_qsyncall(shared_ptr<ProxyServer::LinkedSession>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
G_SyncQuestData_6x77 cmd;
|
G_SyncQuestRegister_6x77 cmd;
|
||||||
cmd.header = {0x77, 0x03, 0x0000};
|
cmd.header = {0x77, 0x03, 0x0000};
|
||||||
cmd.register_number = stoul(tokens[0].substr(1), nullptr, 0);
|
cmd.register_number = stoul(tokens[0].substr(1), nullptr, 0);
|
||||||
cmd.unused = 0;
|
cmd.unused = 0;
|
||||||
|
|||||||
@@ -5034,10 +5034,10 @@ struct G_SetEntitySetFlags_6x76 {
|
|||||||
le_uint16_t flags = 0;
|
le_uint16_t flags = 0;
|
||||||
} __packed_ws__(G_SetEntitySetFlags_6x76, 8);
|
} __packed_ws__(G_SetEntitySetFlags_6x76, 8);
|
||||||
|
|
||||||
// 6x77: Sync quest data
|
// 6x77: Sync quest register
|
||||||
// This is sent by the client when an opcode D9 is executed within a quest.
|
// This is sent by the client when an opcode D9 is executed within a quest.
|
||||||
|
|
||||||
struct G_SyncQuestData_6x77 {
|
struct G_SyncQuestRegister_6x77 {
|
||||||
G_UnusedHeader header;
|
G_UnusedHeader header;
|
||||||
le_uint16_t register_number = 0; // Must be < 0x100
|
le_uint16_t register_number = 0; // Must be < 0x100
|
||||||
le_uint16_t unused = 0;
|
le_uint16_t unused = 0;
|
||||||
@@ -5045,7 +5045,7 @@ struct G_SyncQuestData_6x77 {
|
|||||||
le_uint32_t as_int;
|
le_uint32_t as_int;
|
||||||
le_float as_float;
|
le_float as_float;
|
||||||
} __packed__ value;
|
} __packed__ value;
|
||||||
} __packed_ws__(G_SyncQuestData_6x77, 0x0C);
|
} __packed_ws__(G_SyncQuestRegister_6x77, 0x0C);
|
||||||
|
|
||||||
// 6x78: Unknown
|
// 6x78: Unknown
|
||||||
|
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ JSON HTTPServer::generate_quest_json_st(shared_ptr<const Quest> q) {
|
|||||||
{"Number", q->quest_number},
|
{"Number", q->quest_number},
|
||||||
{"Episode", name_for_episode(q->episode)},
|
{"Episode", name_for_episode(q->episode)},
|
||||||
{"Joinable", q->joinable},
|
{"Joinable", q->joinable},
|
||||||
|
{"LockStatusRegister", (q->lock_status_register >= 0) ? q->lock_status_register : JSON(nullptr)},
|
||||||
{"Name", q->name},
|
{"Name", q->name},
|
||||||
{"BattleRules", std::move(battle_rules_json)},
|
{"BattleRules", std::move(battle_rules_json)},
|
||||||
{"ChallengeTemplateIndex", std::move(challenge_template_index_json)},
|
{"ChallengeTemplateIndex", std::move(challenge_template_index_json)},
|
||||||
|
|||||||
+16
-9
@@ -206,11 +206,13 @@ VersionedQuest::VersionedQuest(
|
|||||||
ssize_t challenge_template_index,
|
ssize_t challenge_template_index,
|
||||||
std::shared_ptr<const IntegralExpression> available_expression,
|
std::shared_ptr<const IntegralExpression> available_expression,
|
||||||
std::shared_ptr<const IntegralExpression> enabled_expression,
|
std::shared_ptr<const IntegralExpression> enabled_expression,
|
||||||
bool force_joinable)
|
bool force_joinable,
|
||||||
|
int16_t lock_status_register)
|
||||||
: quest_number(quest_number),
|
: quest_number(quest_number),
|
||||||
category_id(category_id),
|
category_id(category_id),
|
||||||
episode(Episode::NONE),
|
episode(Episode::NONE),
|
||||||
joinable(false),
|
joinable(force_joinable),
|
||||||
|
lock_status_register(lock_status_register),
|
||||||
version(version),
|
version(version),
|
||||||
language(language),
|
language(language),
|
||||||
is_dlq_encoded(false),
|
is_dlq_encoded(false),
|
||||||
@@ -234,7 +236,6 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderDCNTE*>(bin_decompressed.data());
|
auto* header = reinterpret_cast<const PSOQuestHeaderDCNTE*>(bin_decompressed.data());
|
||||||
this->joinable = force_joinable;
|
|
||||||
this->episode = Episode::EP1;
|
this->episode = Episode::EP1;
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = fnv1a32(header, sizeof(header)) & 0xFFFF;
|
this->quest_number = fnv1a32(header, sizeof(header)) & 0xFFFF;
|
||||||
@@ -250,7 +251,6 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderDC*>(bin_decompressed.data());
|
auto* header = reinterpret_cast<const PSOQuestHeaderDC*>(bin_decompressed.data());
|
||||||
this->joinable = force_joinable;
|
|
||||||
this->episode = Episode::EP1;
|
this->episode = Episode::EP1;
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = header->quest_number;
|
this->quest_number = header->quest_number;
|
||||||
@@ -267,7 +267,6 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderPC*>(bin_decompressed.data());
|
auto* header = reinterpret_cast<const PSOQuestHeaderPC*>(bin_decompressed.data());
|
||||||
this->joinable = force_joinable;
|
|
||||||
this->episode = Episode::EP1;
|
this->episode = Episode::EP1;
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = header->quest_number;
|
this->quest_number = header->quest_number;
|
||||||
@@ -290,7 +289,6 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is incorrect size");
|
throw invalid_argument("file is incorrect size");
|
||||||
}
|
}
|
||||||
auto* map = reinterpret_cast<const Episode3::MapDefinition*>(bin_decompressed.data());
|
auto* map = reinterpret_cast<const Episode3::MapDefinition*>(bin_decompressed.data());
|
||||||
this->joinable = force_joinable;
|
|
||||||
this->episode = Episode::EP3;
|
this->episode = Episode::EP3;
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = map->map_number;
|
this->quest_number = map->map_number;
|
||||||
@@ -308,7 +306,6 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderGC*>(bin_decompressed.data());
|
auto* header = reinterpret_cast<const PSOQuestHeaderGC*>(bin_decompressed.data());
|
||||||
this->joinable = force_joinable;
|
|
||||||
this->episode = find_quest_episode_from_script(bin_decompressed.data(), bin_decompressed.size(), this->version);
|
this->episode = find_quest_episode_from_script(bin_decompressed.data(), bin_decompressed.size(), this->version);
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = header->quest_number;
|
this->quest_number = header->quest_number;
|
||||||
@@ -324,7 +321,7 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderBB*>(bin_decompressed.data());
|
auto* header = reinterpret_cast<const PSOQuestHeaderBB*>(bin_decompressed.data());
|
||||||
this->joinable = header->joinable || force_joinable;
|
this->joinable |= header->joinable;
|
||||||
this->episode = find_quest_episode_from_script(bin_decompressed.data(), bin_decompressed.size(), this->version);
|
this->episode = find_quest_episode_from_script(bin_decompressed.data(), bin_decompressed.size(), this->version);
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = header->quest_number;
|
this->quest_number = header->quest_number;
|
||||||
@@ -388,6 +385,7 @@ Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
|
|||||||
category_id(initial_version->category_id),
|
category_id(initial_version->category_id),
|
||||||
episode(initial_version->episode),
|
episode(initial_version->episode),
|
||||||
joinable(initial_version->joinable),
|
joinable(initial_version->joinable),
|
||||||
|
lock_status_register(initial_version->lock_status_register),
|
||||||
name(initial_version->name),
|
name(initial_version->name),
|
||||||
battle_rules(initial_version->battle_rules),
|
battle_rules(initial_version->battle_rules),
|
||||||
challenge_template_index(initial_version->challenge_template_index),
|
challenge_template_index(initial_version->challenge_template_index),
|
||||||
@@ -413,6 +411,9 @@ void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
|
|||||||
if (this->joinable != vq->joinable) {
|
if (this->joinable != vq->joinable) {
|
||||||
throw runtime_error("quest version has a different joinability state");
|
throw runtime_error("quest version has a different joinability state");
|
||||||
}
|
}
|
||||||
|
if (this->lock_status_register != vq->lock_status_register) {
|
||||||
|
throw runtime_error("quest version has a different lock status register");
|
||||||
|
}
|
||||||
if (!this->battle_rules != !vq->battle_rules) {
|
if (!this->battle_rules != !vq->battle_rules) {
|
||||||
throw runtime_error("quest version has a different battle rules presence state");
|
throw runtime_error("quest version has a different battle rules presence state");
|
||||||
}
|
}
|
||||||
@@ -676,6 +677,7 @@ QuestIndex::QuestIndex(
|
|||||||
shared_ptr<const IntegralExpression> available_expression;
|
shared_ptr<const IntegralExpression> available_expression;
|
||||||
shared_ptr<const IntegralExpression> enabled_expression;
|
shared_ptr<const IntegralExpression> enabled_expression;
|
||||||
bool force_joinable = false;
|
bool force_joinable = false;
|
||||||
|
int16_t lock_status_register = -1;
|
||||||
try {
|
try {
|
||||||
json_filedata = &json_files.at(basename);
|
json_filedata = &json_files.at(basename);
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
@@ -710,6 +712,10 @@ QuestIndex::QuestIndex(
|
|||||||
force_joinable = metadata_json.get_bool("Joinable");
|
force_joinable = metadata_json.get_bool("Joinable");
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
lock_status_register = metadata_json.get_bool("LockStatusRegister");
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto vq = make_shared<VersionedQuest>(
|
auto vq = make_shared<VersionedQuest>(
|
||||||
@@ -724,7 +730,8 @@ QuestIndex::QuestIndex(
|
|||||||
challenge_template_index,
|
challenge_template_index,
|
||||||
available_expression,
|
available_expression,
|
||||||
enabled_expression,
|
enabled_expression,
|
||||||
force_joinable);
|
force_joinable,
|
||||||
|
lock_status_register);
|
||||||
|
|
||||||
auto category_name = this->category_index->at(vq->category_id)->name;
|
auto category_name = this->category_index->at(vq->category_id)->name;
|
||||||
string filenames_str = bin_filedata->filename;
|
string filenames_str = bin_filedata->filename;
|
||||||
|
|||||||
+4
-1
@@ -64,6 +64,7 @@ struct VersionedQuest {
|
|||||||
uint32_t category_id;
|
uint32_t category_id;
|
||||||
Episode episode;
|
Episode episode;
|
||||||
bool joinable;
|
bool joinable;
|
||||||
|
int16_t lock_status_register;
|
||||||
std::string name;
|
std::string name;
|
||||||
Version version;
|
Version version;
|
||||||
uint8_t language;
|
uint8_t language;
|
||||||
@@ -91,7 +92,8 @@ struct VersionedQuest {
|
|||||||
ssize_t challenge_template_index = -1,
|
ssize_t challenge_template_index = -1,
|
||||||
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
|
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
|
||||||
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr,
|
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr,
|
||||||
bool force_joinable = false);
|
bool force_joinable = false,
|
||||||
|
int16_t lock_status_register = -1);
|
||||||
|
|
||||||
std::string bin_filename() const;
|
std::string bin_filename() const;
|
||||||
std::string dat_filename() const;
|
std::string dat_filename() const;
|
||||||
@@ -122,6 +124,7 @@ public:
|
|||||||
uint32_t category_id;
|
uint32_t category_id;
|
||||||
Episode episode;
|
Episode episode;
|
||||||
bool joinable;
|
bool joinable;
|
||||||
|
int16_t lock_status_register;
|
||||||
std::string name;
|
std::string name;
|
||||||
std::shared_ptr<const BattleRules> battle_rules;
|
std::shared_ptr<const BattleRules> battle_rules;
|
||||||
ssize_t challenge_template_index;
|
ssize_t challenge_template_index;
|
||||||
|
|||||||
@@ -2803,6 +2803,36 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void on_sync_quest_register(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||||
|
auto l = c->require_lobby();
|
||||||
|
if (!l->is_game()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& cmd = check_size_t<G_SyncQuestRegister_6x77>(data, size);
|
||||||
|
if (cmd.register_number >= 0x100) {
|
||||||
|
throw runtime_error("invalid register number");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the lock status register is being written, change the game's flags to
|
||||||
|
// allow or forbid joining
|
||||||
|
if (l->quest &&
|
||||||
|
l->quest->joinable &&
|
||||||
|
(l->quest->lock_status_register >= 0) &&
|
||||||
|
(cmd.register_number == l->quest->lock_status_register)) {
|
||||||
|
// Lock if value is nonzero; unlock if value is zero
|
||||||
|
if (cmd.value.as_int) {
|
||||||
|
l->set_flag(Lobby::Flag::QUEST_IN_PROGRESS);
|
||||||
|
l->clear_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS);
|
||||||
|
} else {
|
||||||
|
l->clear_flag(Lobby::Flag::QUEST_IN_PROGRESS);
|
||||||
|
l->set_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_subcommand(c, command, flag, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
static void on_set_entity_set_flag(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
static void on_set_entity_set_flag(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (!l->is_game()) {
|
if (!l->is_game()) {
|
||||||
@@ -4480,7 +4510,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
|||||||
/* 6x74 */ {0x62, 0x69, 0x74, on_word_select, SDF::ALWAYS_FORWARD_TO_WATCHERS},
|
/* 6x74 */ {0x62, 0x69, 0x74, on_word_select, SDF::ALWAYS_FORWARD_TO_WATCHERS},
|
||||||
/* 6x75 */ {0x00, 0x00, 0x75, on_set_quest_flag},
|
/* 6x75 */ {0x00, 0x00, 0x75, on_set_quest_flag},
|
||||||
/* 6x76 */ {0x00, 0x00, 0x76, on_set_entity_set_flag},
|
/* 6x76 */ {0x00, 0x00, 0x76, on_set_entity_set_flag},
|
||||||
/* 6x77 */ {0x00, 0x00, 0x77, on_forward_check_game},
|
/* 6x77 */ {0x00, 0x00, 0x77, on_sync_quest_register},
|
||||||
/* 6x78 */ {0x00, 0x00, 0x78, forward_subcommand_m},
|
/* 6x78 */ {0x00, 0x00, 0x78, forward_subcommand_m},
|
||||||
/* 6x79 */ {0x00, 0x00, 0x79, on_forward_check_lobby},
|
/* 6x79 */ {0x00, 0x00, 0x79, on_forward_check_lobby},
|
||||||
/* 6x7A */ {0x00, 0x00, 0x7A, on_forward_check_game_client},
|
/* 6x7A */ {0x00, 0x00, 0x7A, on_forward_check_game_client},
|
||||||
|
|||||||
@@ -61,4 +61,10 @@
|
|||||||
// progress. Note that this will likely not work properly unless the quest
|
// progress. Note that this will likely not work properly unless the quest
|
||||||
// script is designed to support joining players.
|
// script is designed to support joining players.
|
||||||
// "Joinable": true,
|
// "Joinable": true,
|
||||||
|
// If a quest is joinable and this field is specified, then setting this
|
||||||
|
// register to a nonzero value via the sync_register opcode will cause the
|
||||||
|
// game to be locked and not allow any further joins. The register may be
|
||||||
|
// set to to zero to allow joins again. This field is ignored for quests
|
||||||
|
// that do not have the Joinable otion set above.
|
||||||
|
// "LockStatusRegister": 249,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user