extend persistence to enemy, set, and switch flags
This commit is contained in:
+18
-2
@@ -1471,18 +1471,34 @@ static void server_command_warp(shared_ptr<Client> c, const std::string& args, b
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(l, c);
|
||||
|
||||
uint32_t floor = stoul(args, nullptr, 0);
|
||||
if (c->floor == floor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case: $warp[me] 0 is allowed in boss arenas if the boss is already
|
||||
// defeated, even if cheats are disabled. This is because if a player returns
|
||||
// to a boss arena after a persistence gap in the game, the exit warp won't
|
||||
// exist, so they need a way to get out.
|
||||
bool should_check_cheats = is_warpall || (floor != 0) || !floor_is_boss_arena(l->episode, c->floor);
|
||||
if (!should_check_cheats) {
|
||||
for (const auto* event : l->map->get_events(c->floor)) {
|
||||
if (!(event->flags & 0x18)) {
|
||||
should_check_cheats = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (should_check_cheats) {
|
||||
check_cheats_enabled(l, c);
|
||||
}
|
||||
|
||||
size_t limit = floor_limit_for_episode(l->episode);
|
||||
if (limit == 0) {
|
||||
return;
|
||||
} else if (floor > limit) {
|
||||
send_text_message_printf(c, "$C6Area numbers must\nbe %zu or less.", limit);
|
||||
send_text_message_printf(c, "$C6Area numbers must\nbe %zu or less", limit);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+24
-16
@@ -3828,21 +3828,28 @@ struct G_ExtendedHeader {
|
||||
struct G_Unknown_6x04 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unused = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x05: Switch state changed
|
||||
// Some things that don't look like switches are implemented as switches using
|
||||
// this subcommand. For example, when all enemies in a room are defeated, this
|
||||
// subcommand is used to unlock the doors.
|
||||
// Note: In the client, this is a subclass of 6x04, similar to how 6xA2 is a
|
||||
// subclass of 6x60.
|
||||
|
||||
struct G_SwitchStateChanged_6x05 {
|
||||
// Note: header.object_id is 0xFFFF for room clear when all enemies defeated
|
||||
G_ObjectIDHeader header;
|
||||
parray<uint8_t, 2> unknown_a1;
|
||||
// TODO: Some of these might be big-endian on GC; it only byteswaps
|
||||
// unknown_a3. Are the others actually uint16, or are they uint8[2]?
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
parray<uint8_t, 2> unknown_a3;
|
||||
uint8_t floor = 0;
|
||||
le_uint16_t switch_flag_num = 0;
|
||||
uint8_t switch_flag_floor = 0;
|
||||
// Only two bits in flags have meanings:
|
||||
// 01 - set unlock flag (if not set, the flag is cleared instead)
|
||||
// 02 - play room unlock sound if floor matches client's floor
|
||||
uint8_t flags = 0; // Bit field, with 2 lowest bits having meaning
|
||||
} __packed__;
|
||||
|
||||
@@ -4635,7 +4642,7 @@ struct G_TriggerSetEvent_6x67 {
|
||||
G_UnusedHeader header;
|
||||
le_uint32_t floor = 0;
|
||||
le_uint32_t event_id = 0; // NOT event index
|
||||
le_uint32_t unused = 0;
|
||||
le_uint32_t client_id = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x68: Create telepipe / cast Ryuker
|
||||
@@ -4754,11 +4761,11 @@ struct G_SyncSetFlagState_6x6E_Decompressed {
|
||||
le_uint16_t total_size = 0; // == sum of the following 3 fields
|
||||
le_uint16_t entity_set_flags_size = 0;
|
||||
le_uint16_t event_set_flags_size = 0;
|
||||
le_uint16_t unused_size = 0;
|
||||
le_uint16_t switch_flags_size = 0;
|
||||
// Variable-length fields follow here:
|
||||
// EntitySetFlags entity_set_flags; // Total size is set_flags_size
|
||||
// le_uint16_t event_set_flags[event_set_flags_size / 2]; // Same order as in map files (NOT sorted by event_id)
|
||||
// uint8_t unused[is_v1 ? 0x200 : 0x240]; // Possibly an early implementation of 6x6F; unused even in DC NTE
|
||||
// SwitchFlags switch_flags; // 0x200 bytes on v1 abd earlier; 0x240 bytes on v2 and later
|
||||
|
||||
struct EntitySetFlags {
|
||||
le_uint32_t object_set_flags_offset = 0;
|
||||
@@ -4993,11 +5000,11 @@ struct G_UpdateQuestFlag_V3_BB_6x75 : G_UpdateQuestFlag_DC_PC_6x75 {
|
||||
le_uint16_t unused = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x76: Set entity flags
|
||||
// This command can only be used to set flags, since the game performs a bitwise
|
||||
// OR operation instead of a simple assignment.
|
||||
// 6x76: Set entity set flags
|
||||
// This command can only be used to set set flags, since the game performs a
|
||||
// bitwise OR operation instead of a simple assignment.
|
||||
|
||||
struct G_SetEntityFlags_6x76 {
|
||||
struct G_SetEntitySetFlags_6x76 {
|
||||
G_EnemyIDHeader header; // 1000-3FFF = enemy, 4000-FFFF = object
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t flags = 0;
|
||||
@@ -5235,8 +5242,9 @@ struct G_Unknown_6x91 {
|
||||
le_uint32_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint16_t unknown_a4 = 0;
|
||||
le_uint16_t unknown_a5 = 0;
|
||||
parray<uint8_t, 2> unknown_a6;
|
||||
le_uint16_t switch_flag_num = 0;
|
||||
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared
|
||||
uint8_t switch_flag_floor = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x92: Unknown (not valid on Episode 3)
|
||||
@@ -5251,9 +5259,9 @@ struct G_Unknown_6x92 {
|
||||
|
||||
struct G_ActivateTimedSwitch_6x93 {
|
||||
G_UnusedHeader header;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t switch_id = 0;
|
||||
uint8_t unknown_a1 = 0; // Logic is different if this is 1 vs. any other value
|
||||
le_uint16_t switch_flag_floor = 0;
|
||||
le_uint16_t switch_flag_num = 0;
|
||||
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared
|
||||
parray<uint8_t, 3> unused;
|
||||
} __packed__;
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
std::unique_ptr<QuestFlags> quest_flags_known; // If null, ALL quest flags are known
|
||||
std::unique_ptr<QuestFlags> quest_flag_values;
|
||||
std::unique_ptr<SwitchFlags> switch_flags;
|
||||
|
||||
// Game config
|
||||
Version base_version;
|
||||
|
||||
+37
@@ -1115,6 +1115,43 @@ Action a_disassemble_quest_map(
|
||||
data = prs_decompress(data);
|
||||
}
|
||||
string result = Map::disassemble_quest_data(data.data(), data.size());
|
||||
write_output_data(args, result.data(), result.size(), "txt");
|
||||
});
|
||||
Action a_disassemble_free_map(
|
||||
"disassemble-free-map", "\
|
||||
disassemble-free-map INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
||||
Disassemble the input free-roam map (.dat or .evt file) into a text\n\
|
||||
representation of the data it contains. Unlike othe disassembly actions,\n\
|
||||
this action expects its input to be already decompressed. If the input is\n\
|
||||
compressed, use the --compressed option. Also unlike other options, the\n\
|
||||
input must be from a file (that is, INPUT-FILENAME is required and cannot\n\
|
||||
be \"-\").\n",
|
||||
+[](Arguments& args) {
|
||||
const string& input_filename = args.get<string>(1, true);
|
||||
bool is_events = ends_with(input_filename, ".evt");
|
||||
bool is_enemies = ends_with(input_filename, "e.dat") || ends_with(input_filename, "e_s.dat") || ends_with(input_filename, "e_c1.dat") || ends_with(input_filename, "e_d.dat");
|
||||
bool is_objects = ends_with(input_filename, "o.dat") || ends_with(input_filename, "o_s.dat") || ends_with(input_filename, "o_c1.dat") || ends_with(input_filename, "o_d.dat");
|
||||
if (!is_objects && !is_enemies && !is_events) {
|
||||
throw runtime_error("cannot determine input file type");
|
||||
}
|
||||
|
||||
string data = read_input_data(args);
|
||||
if (args.get<bool>("compressed")) {
|
||||
data = prs_decompress(data);
|
||||
}
|
||||
|
||||
string result;
|
||||
if (is_objects) {
|
||||
result = Map::disassemble_objects_data(data.data(), data.size());
|
||||
} else if (is_enemies) {
|
||||
result = Map::disassemble_enemies_data(data.data(), data.size());
|
||||
} else if (is_events) {
|
||||
result = Map::disassemble_wave_events_data(data.data(), data.size());
|
||||
} else {
|
||||
throw logic_error("unhandled input type");
|
||||
}
|
||||
result.push_back('\n');
|
||||
|
||||
write_output_data(args, result.data(), result.size(), "txt");
|
||||
});
|
||||
Action a_disassemble_set_data_table(
|
||||
|
||||
+187
-36
@@ -14,6 +14,10 @@ using namespace std;
|
||||
|
||||
static constexpr float UINT32_MAX_AS_FLOAT = 4294967296.0f;
|
||||
|
||||
static uint64_t section_index_key(uint8_t floor, uint16_t section, uint16_t wave_number) {
|
||||
return (static_cast<uint64_t>(floor) << 32) | (static_cast<uint64_t>(section) << 16) | static_cast<uint64_t>(wave_number);
|
||||
}
|
||||
|
||||
const char* Map::name_for_object_type(uint16_t type) {
|
||||
switch (type) {
|
||||
case 0x0000:
|
||||
@@ -655,23 +659,34 @@ string Map::EnemyEntry::str() const {
|
||||
this->unused.load());
|
||||
}
|
||||
|
||||
Map::Enemy::Enemy(uint16_t enemy_id, size_t source_index, size_t set_index, uint8_t floor, EnemyType type)
|
||||
Map::Enemy::Enemy(
|
||||
uint16_t enemy_id,
|
||||
size_t source_index,
|
||||
size_t set_index,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
EnemyType type)
|
||||
: source_index(source_index),
|
||||
set_index(set_index),
|
||||
enemy_id(enemy_id),
|
||||
total_damage(0),
|
||||
game_flags(0),
|
||||
section(section),
|
||||
wave_number(wave_number),
|
||||
type(type),
|
||||
floor(floor),
|
||||
state_flags(0) {}
|
||||
|
||||
string Map::Enemy::str() const {
|
||||
return string_printf("[Map::Enemy E-%hX source %zX %s%s floor=%02hhX flags=%02hhX]",
|
||||
return string_printf("[Map::Enemy E-%hX source %zX %s%s floor=%02hhX section=%04hX wave_number=%04hX flags=%02hhX]",
|
||||
this->enemy_id,
|
||||
this->source_index,
|
||||
name_for_enum(this->type),
|
||||
enemy_type_is_rare(this->type) ? " RARE" : "",
|
||||
this->floor,
|
||||
this->section,
|
||||
this->wave_number,
|
||||
this->state_flags);
|
||||
}
|
||||
|
||||
@@ -725,6 +740,7 @@ void Map::add_objects_from_map_data(uint8_t floor, const void* data, size_t size
|
||||
.floor = floor,
|
||||
.base_type = objects[z].base_type,
|
||||
.section = objects[z].section,
|
||||
.group = objects[z].group,
|
||||
.param1 = objects[z].param1,
|
||||
.param3 = objects[z].param3,
|
||||
.param4 = objects[z].param4,
|
||||
@@ -734,6 +750,8 @@ void Map::add_objects_from_map_data(uint8_t floor, const void* data, size_t size
|
||||
.set_flags = 0,
|
||||
.item_drop_checked = false,
|
||||
});
|
||||
uint64_t k = section_index_key(floor, objects[z].section, objects[z].group);
|
||||
this->floor_section_and_group_to_object_index.emplace(k, object_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,7 +800,9 @@ void Map::add_enemy(
|
||||
|
||||
auto add = [&](EnemyType type) -> void {
|
||||
uint16_t enemy_id = this->enemies.size();
|
||||
this->enemies.emplace_back(enemy_id, source_index, set_index, floor, type);
|
||||
this->enemies.emplace_back(enemy_id, source_index, set_index, floor, e.section, e.wave_number, type);
|
||||
uint64_t k = section_index_key(floor, e.section, e.wave_number);
|
||||
this->floor_section_and_wave_number_to_enemy_index.emplace(k, enemy_id);
|
||||
};
|
||||
|
||||
EnemyType child_type = EnemyType::UNKNOWN;
|
||||
@@ -1434,7 +1454,7 @@ void Map::add_random_enemies_from_map_data(
|
||||
}
|
||||
if (remaining_waves) {
|
||||
/* ev.delay = */ random_state->rand_int_biased(entry.min_delay, entry.max_delay);
|
||||
this->add_event(wave_next_event_id, entry.flags, floor, this->event_action_stream.size());
|
||||
this->add_event(wave_next_event_id, entry.flags, floor, entry.section, wave_number, this->event_action_stream.size());
|
||||
this->event_action_stream.push_back(0x0C);
|
||||
wave_next_event_id = entry.event_id + wave_number + 10000;
|
||||
this->event_action_stream.append(reinterpret_cast<const char*>(&wave_next_event_id), sizeof(wave_next_event_id));
|
||||
@@ -1444,15 +1464,17 @@ void Map::add_random_enemies_from_map_data(
|
||||
}
|
||||
|
||||
/* ev.delay = */ random_state->rand_int_biased(entry.min_delay, entry.max_delay);
|
||||
this->add_event(wave_next_event_id, entry.flags, floor, action_stream_base_offset + entry.action_stream_offset);
|
||||
this->add_event(wave_next_event_id, entry.flags, floor, entry.section, wave_number, action_stream_base_offset + entry.action_stream_offset);
|
||||
wave_number++;
|
||||
}
|
||||
}
|
||||
|
||||
void Map::add_event(uint32_t event_id, uint16_t flags, uint8_t floor, uint32_t action_stream_offset) {
|
||||
void Map::add_event(uint32_t event_id, uint16_t flags, uint8_t floor, uint16_t section, uint16_t wave_number, uint32_t action_stream_offset) {
|
||||
size_t index = this->events.size();
|
||||
auto& ev = this->events.emplace_back();
|
||||
ev.event_id = event_id;
|
||||
ev.section = section;
|
||||
ev.wave_number = wave_number;
|
||||
ev.flags = flags;
|
||||
ev.floor = floor;
|
||||
ev.action_stream_offset = action_stream_offset;
|
||||
@@ -1460,6 +1482,9 @@ void Map::add_event(uint32_t event_id, uint16_t flags, uint8_t floor, uint32_t a
|
||||
if (!this->floor_and_event_id_to_index.emplace(k, index).second) {
|
||||
this->log.warning("Duplicate event ID: W-%02hhX-%" PRIX32, floor, event_id);
|
||||
}
|
||||
|
||||
k = section_index_key(floor, section, wave_number);
|
||||
this->floor_section_and_wave_number_to_event_index.emplace(k, index);
|
||||
}
|
||||
|
||||
Map::Event& Map::get_event(uint8_t floor, uint32_t event_id) {
|
||||
@@ -1486,7 +1511,7 @@ void Map::add_events_from_map_data(uint8_t floor, const void* data, size_t size)
|
||||
auto events_r = r.sub(header.entries_offset, sizeof(Event1Entry) * header.entry_count);
|
||||
while (!events_r.eof()) {
|
||||
const auto& entry = events_r.get<Event1Entry>();
|
||||
this->add_event(entry.event_id, entry.flags, floor, entry.action_stream_offset + action_stream_base_offset);
|
||||
this->add_event(entry.event_id, entry.flags, floor, entry.section, entry.wave_number, entry.action_stream_offset + action_stream_base_offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1638,6 +1663,154 @@ Map::Enemy& Map::find_enemy(uint8_t floor, EnemyType type) {
|
||||
throw out_of_range("enemy not found");
|
||||
}
|
||||
|
||||
std::vector<Map::Object*> Map::get_objects(uint8_t floor, uint16_t section, uint16_t group) {
|
||||
uint64_t k = section_index_key(floor, section, group);
|
||||
vector<Object*> ret;
|
||||
for (auto its = this->floor_section_and_group_to_object_index.equal_range(k); its.first != its.second; its.first++) {
|
||||
ret.emplace_back(&this->objects.at(its.first->second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Map::Enemy*> Map::get_enemies(uint8_t floor, uint16_t section, uint16_t wave_number) {
|
||||
uint64_t k = section_index_key(floor, section, wave_number);
|
||||
vector<Enemy*> ret;
|
||||
for (auto its = this->floor_section_and_wave_number_to_enemy_index.equal_range(k); its.first != its.second; its.first++) {
|
||||
ret.emplace_back(&this->enemies.at(its.first->second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Map::Event*> Map::get_events(uint8_t floor, uint16_t section, uint16_t wave_number) {
|
||||
uint64_t k = section_index_key(floor, section, wave_number);
|
||||
vector<Event*> ret;
|
||||
for (auto its = this->floor_section_and_wave_number_to_event_index.equal_range(k); its.first != its.second; its.first++) {
|
||||
ret.emplace_back(&this->events.at(its.first->second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Map::Event*> Map::get_events(uint8_t floor) {
|
||||
uint64_t k_start = (static_cast<uint64_t>(floor) << 32);
|
||||
uint64_t k_end = (static_cast<uint64_t>(floor + 1) << 32);
|
||||
vector<Event*> ret;
|
||||
for (auto it = this->floor_and_event_id_to_index.lower_bound(k_start);
|
||||
(it != this->floor_and_event_id_to_index.end()) && (it->first < k_end);
|
||||
it++) {
|
||||
ret.emplace_back(&this->events.at(it->second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename EntryT>
|
||||
static string disassemble_vector_file_t(const void* data, size_t size, size_t* entry_number, char type_ch) {
|
||||
deque<string> ret;
|
||||
StringReader r(data, size);
|
||||
|
||||
size_t local_entry_number = 0;
|
||||
if (!entry_number) {
|
||||
entry_number = &local_entry_number;
|
||||
}
|
||||
|
||||
while (r.remaining() >= sizeof(EntryT)) {
|
||||
string o_str = r.get<EntryT>().str();
|
||||
ret.emplace_back(string_printf("/* %c-%zX */ %s", type_ch, (*entry_number)++, o_str.c_str()));
|
||||
}
|
||||
if (r.remaining()) {
|
||||
ret.emplace_back("// Warning: section size is not a multiple of entry size");
|
||||
size_t size = r.remaining();
|
||||
ret.emplace_back(format_data(r.getv(size), size));
|
||||
}
|
||||
return join(ret, "\n");
|
||||
}
|
||||
|
||||
string Map::disassemble_objects_data(const void* data, size_t size, size_t* object_number) {
|
||||
return disassemble_vector_file_t<ObjectEntry>(data, size, object_number, 'K');
|
||||
}
|
||||
|
||||
string Map::disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number) {
|
||||
return disassemble_vector_file_t<EnemyEntry>(data, size, enemy_number, 'S');
|
||||
}
|
||||
|
||||
string Map::disassemble_wave_events_data(const void* data, size_t size, uint8_t floor) {
|
||||
deque<string> ret;
|
||||
StringReader r(data, size);
|
||||
|
||||
const auto& evt_header = r.get<EventsSectionHeader>();
|
||||
if (evt_header.format == 0x65767432) { // 'evt2'
|
||||
ret.emplace_back(".evt2_format"); // TODO
|
||||
size_t size = r.remaining();
|
||||
ret.emplace_back(format_data(r.getv(size), size));
|
||||
} else {
|
||||
auto action_stream_r = r.sub(evt_header.action_stream_offset);
|
||||
for (size_t z = 0; z < evt_header.entry_count; z++) {
|
||||
const auto& entry = r.get<Event1Entry>();
|
||||
ret.emplace_back(string_printf("/* W-%02hhX-%" PRIX32 " */ [Event1Entry flags=%04hX type=%04hX section=%04hX wave_number=%04hX delay=%" PRIu32 "]",
|
||||
floor,
|
||||
entry.event_id.load(),
|
||||
entry.flags.load(),
|
||||
entry.event_type.load(),
|
||||
entry.section.load(),
|
||||
entry.wave_number.load(),
|
||||
entry.delay.load()));
|
||||
auto ev_actions_r = action_stream_r.sub(entry.action_stream_offset);
|
||||
bool should_continue = true;
|
||||
while (!ev_actions_r.eof() && should_continue) {
|
||||
uint8_t opcode = ev_actions_r.get_u8();
|
||||
switch (opcode) {
|
||||
case 0x00:
|
||||
ret.emplace_back(string_printf(" 00 nop"));
|
||||
break;
|
||||
case 0x01:
|
||||
ret.emplace_back(string_printf(" 01 stop"));
|
||||
should_continue = false;
|
||||
break;
|
||||
case 0x08: {
|
||||
uint16_t section = ev_actions_r.get_u16l();
|
||||
uint16_t group = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 08 %04hX %04hX construct_objects section=%04hX group=%04hX",
|
||||
section, group, section, group));
|
||||
break;
|
||||
}
|
||||
case 0x09: {
|
||||
uint16_t section = ev_actions_r.get_u16l();
|
||||
uint16_t wave_number = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 09 %04hX %04hX construct_enemies section=%04hX wave_number=%04hX",
|
||||
section, wave_number, section, wave_number));
|
||||
break;
|
||||
}
|
||||
case 0x0A: {
|
||||
uint16_t id = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 0A %04hX enable_switch_flag id=%04hX", id, id));
|
||||
break;
|
||||
}
|
||||
case 0x0B: {
|
||||
uint16_t id = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 0B %04hX disable_switch_flag id=%04hX", id, id));
|
||||
break;
|
||||
}
|
||||
case 0x0C: {
|
||||
uint32_t event_id = ev_actions_r.get_u32l();
|
||||
ret.emplace_back(string_printf(" 0C %04hX trigger_event event_id=%08" PRIX32, event_id, event_id));
|
||||
break;
|
||||
}
|
||||
case 0x0D: {
|
||||
uint16_t section = ev_actions_r.get_u16l();
|
||||
uint16_t wave_number = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 0D %04hX %04hX construct_enemies_stop section=%04hX wave_number=%04hX",
|
||||
section, wave_number, section, wave_number));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret.emplace_back(string_printf(" %02hhX .invalid", opcode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return join(ret, "\n");
|
||||
}
|
||||
|
||||
string Map::disassemble_quest_data(const void* data, size_t size) {
|
||||
auto all_floor_sections = Map::collect_quest_map_data_sections(data, size);
|
||||
|
||||
@@ -1651,56 +1824,34 @@ string Map::disassemble_quest_data(const void* data, size_t size) {
|
||||
if (floor_sections.objects != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".objects %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.objects);
|
||||
auto sub_r = r.sub(floor_sections.objects + sizeof(SectionHeader), header.data_size);
|
||||
while (sub_r.remaining() >= sizeof(ObjectEntry)) {
|
||||
string o_str = sub_r.get<ObjectEntry>().str();
|
||||
ret.emplace_back(string_printf("/* K-%zX */ %s", object_number++, o_str.c_str()));
|
||||
}
|
||||
if (sub_r.remaining()) {
|
||||
ret.emplace_back("// Warning: object section size is not a multiple of object entry size");
|
||||
size_t offset = floor_sections.objects + sizeof(SectionHeader) + r.where();
|
||||
size_t bytes = r.remaining();
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), bytes, offset));
|
||||
}
|
||||
size_t offset = floor_sections.objects + sizeof(SectionHeader);
|
||||
ret.emplace_back(Map::disassemble_objects_data(r.pgetv(offset, header.data_size), header.data_size, &object_number));
|
||||
}
|
||||
|
||||
if (floor_sections.enemies != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".enemies %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.enemies);
|
||||
auto sub_r = r.sub(floor_sections.enemies + sizeof(SectionHeader), header.data_size);
|
||||
while (sub_r.remaining() >= sizeof(EnemyEntry)) {
|
||||
string e_str = sub_r.get<EnemyEntry>().str();
|
||||
ret.emplace_back(string_printf("/* entry %zX */ %s", enemy_number++, e_str.c_str()));
|
||||
}
|
||||
if (sub_r.remaining()) {
|
||||
ret.emplace_back("// Warning: enemy section size is not a multiple of enemy entry size");
|
||||
size_t offset = floor_sections.objects + sizeof(SectionHeader) + r.where();
|
||||
size_t bytes = r.remaining();
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), bytes, offset));
|
||||
}
|
||||
size_t offset = floor_sections.enemies + sizeof(SectionHeader);
|
||||
ret.emplace_back(Map::disassemble_enemies_data(r.pgetv(offset, header.data_size), header.data_size, &enemy_number));
|
||||
}
|
||||
|
||||
// TODO: Add disassembly for these section types
|
||||
if (floor_sections.wave_events != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".wave_events %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.wave_events);
|
||||
size_t offset = floor_sections.wave_events + sizeof(SectionHeader);
|
||||
auto sub_r = r.sub(offset, header.data_size);
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), header.data_size, offset));
|
||||
ret.emplace_back(Map::disassemble_wave_events_data(r.pgetv(offset, header.data_size), header.data_size, floor));
|
||||
}
|
||||
if (floor_sections.random_enemy_locations != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".random_enemy_locations %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.random_enemy_locations);
|
||||
size_t offset = floor_sections.random_enemy_locations + sizeof(SectionHeader);
|
||||
auto sub_r = r.sub(offset, header.data_size);
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), header.data_size, offset));
|
||||
ret.emplace_back(format_data(sub_r.getv(sub_r.remaining()), header.data_size, offset));
|
||||
}
|
||||
if (floor_sections.random_enemy_definitions != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".random_enemy_definitions %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.random_enemy_definitions);
|
||||
size_t offset = floor_sections.random_enemy_definitions + sizeof(SectionHeader);
|
||||
auto sub_r = r.sub(offset, header.data_size);
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), header.data_size, offset));
|
||||
ret.emplace_back(format_data(sub_r.getv(sub_r.remaining()), header.data_size, offset));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+39
-6
@@ -103,6 +103,10 @@ struct Map {
|
||||
|
||||
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
|
||||
/* 00 */ le_uint32_t event_id;
|
||||
// Bits in flags:
|
||||
// 0004 = is active
|
||||
// 0008 = post-wave actions have been run
|
||||
// 0010 = all enemies killed
|
||||
/* 04 */ le_uint16_t flags;
|
||||
/* 06 */ le_uint16_t event_type;
|
||||
/* 08 */ le_uint16_t section;
|
||||
@@ -208,10 +212,11 @@ struct Map {
|
||||
// TODO: Add more fields in here if we ever care about them. Currently we
|
||||
// only care about boxes with fixed item drops.
|
||||
size_t source_index;
|
||||
uint16_t object_id;
|
||||
uint8_t floor;
|
||||
uint16_t object_id;
|
||||
uint16_t base_type;
|
||||
uint16_t section;
|
||||
uint16_t group;
|
||||
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
|
||||
float param3; // If == 0, the item should be varied by difficulty and area
|
||||
uint32_t param4;
|
||||
@@ -239,23 +244,35 @@ struct Map {
|
||||
uint16_t enemy_id;
|
||||
uint16_t total_damage;
|
||||
uint32_t game_flags; // From 6x0A
|
||||
uint16_t section;
|
||||
uint16_t wave_number;
|
||||
EnemyType type;
|
||||
uint8_t floor;
|
||||
uint8_t state_flags;
|
||||
|
||||
Enemy(uint16_t enemy_id, size_t source_index, size_t set_index, uint8_t floor, EnemyType type);
|
||||
Enemy(
|
||||
uint16_t enemy_id,
|
||||
size_t source_index,
|
||||
size_t set_index,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
EnemyType type);
|
||||
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
struct Event {
|
||||
uint32_t event_id;
|
||||
uint16_t flags;
|
||||
uint16_t section;
|
||||
uint16_t wave_number;
|
||||
uint8_t floor;
|
||||
uint32_t action_stream_offset;
|
||||
std::vector<size_t> enemy_indexes;
|
||||
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
struct DATParserRandomState {
|
||||
PSOV2Encryption random;
|
||||
@@ -306,7 +323,13 @@ struct Map {
|
||||
std::shared_ptr<DATParserRandomState> random_state,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
|
||||
void add_event(uint32_t event_id, uint16_t flags, uint8_t floor, uint32_t action_stream_offset);
|
||||
void add_event(
|
||||
uint32_t event_id,
|
||||
uint16_t flags,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
uint32_t action_stream_offset);
|
||||
Event& get_event(uint8_t floor, uint32_t event_id);
|
||||
const Event& get_event(uint8_t floor, uint32_t event_id) const;
|
||||
void add_events_from_map_data(uint8_t floor, const void* data, size_t size);
|
||||
@@ -330,7 +353,14 @@ struct Map {
|
||||
|
||||
const Enemy& find_enemy(uint8_t floor, EnemyType type) const;
|
||||
Enemy& find_enemy(uint8_t floor, EnemyType type);
|
||||
std::vector<Object*> get_objects(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Enemy*> get_enemies(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Event*> get_events(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Event*> get_events(uint8_t floor);
|
||||
|
||||
static std::string disassemble_objects_data(const void* data, size_t size, size_t* object_number = nullptr);
|
||||
static std::string disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number = nullptr);
|
||||
static std::string disassemble_wave_events_data(const void* data, size_t size, uint8_t floor = 0xFF);
|
||||
static std::string disassemble_quest_data(const void* data, size_t size);
|
||||
|
||||
PrefixedLogger log;
|
||||
@@ -343,7 +373,10 @@ struct Map {
|
||||
std::vector<size_t> rare_enemy_indexes;
|
||||
std::vector<Event> events;
|
||||
std::string event_action_stream;
|
||||
std::unordered_map<uint64_t, size_t> floor_and_event_id_to_index;
|
||||
std::map<uint64_t, size_t> floor_and_event_id_to_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_group_to_object_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_enemy_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_event_index;
|
||||
};
|
||||
|
||||
class SetDataTableBase {
|
||||
|
||||
@@ -565,6 +565,20 @@ struct QuestFlagsV1 {
|
||||
operator QuestFlags() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct SwitchFlags {
|
||||
parray<parray<uint8_t, 0x20>, 0x12> data;
|
||||
|
||||
inline bool get(uint8_t floor, uint16_t flag_num) const {
|
||||
return this->data[floor][flag_num >> 3] & (0x80 >> (flag_num & 7));
|
||||
}
|
||||
inline void set(uint8_t floor, uint16_t flag_num) {
|
||||
this->data[floor][flag_num >> 3] |= (0x80 >> (flag_num & 7));
|
||||
}
|
||||
inline void clear(uint8_t floor, uint16_t flag_num) {
|
||||
this->data[floor][flag_num >> 3] &= ~(0x80 >> (flag_num & 7));
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
struct BattleRules {
|
||||
enum class TechDiskMode : uint8_t {
|
||||
ALLOW = 0,
|
||||
|
||||
+2
-11
@@ -2515,17 +2515,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
// leader)
|
||||
if (game->count_clients() == 1) {
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE);
|
||||
// TODO: Eventually, we want to send the enemy and set states too,
|
||||
// but currently this doesn't work well. Instead, we reset their
|
||||
// flags so it's as if they were never defeated.
|
||||
// c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE);
|
||||
if (game->map) {
|
||||
for (auto& enemy : game->map->enemies) {
|
||||
enemy.game_flags = 0;
|
||||
enemy.total_damage = 0;
|
||||
enemy.state_flags = 0;
|
||||
}
|
||||
}
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE);
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_OBJECT_STATE);
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
}
|
||||
@@ -4329,6 +4319,7 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->quest_flag_values = make_unique<QuestFlags>();
|
||||
game->quest_flags_known = make_unique<QuestFlags>();
|
||||
}
|
||||
game->switch_flags = make_unique<SwitchFlags>();
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
+208
-34
@@ -485,11 +485,10 @@ static void on_sync_joining_player_compressed_state(shared_ptr<Client> c, uint8_
|
||||
send_game_item_state(target);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x6E: {
|
||||
StringReader r(decompressed);
|
||||
const auto& dec_header = r.get<G_SyncSetFlagState_6x6E_Decompressed>();
|
||||
if (dec_header.total_size != dec_header.entity_set_flags_size + dec_header.event_set_flags_size + dec_header.unused_size) {
|
||||
if (dec_header.total_size != dec_header.entity_set_flags_size + dec_header.event_set_flags_size + dec_header.switch_flags_size) {
|
||||
throw runtime_error("incorrect size fields in 6x6E header");
|
||||
}
|
||||
|
||||
@@ -518,8 +517,9 @@ static void on_sync_joining_player_compressed_state(shared_ptr<Client> c, uint8_
|
||||
for (size_t z = 0; z < min<size_t>(set_flags_header.num_object_sets, l->map->objects.size()); z++) {
|
||||
uint16_t flags = set_flags_r.get_u16l();
|
||||
if (flags != l->map->objects[z].set_flags) {
|
||||
l->log.warning("(K-%zX) Set flags from client (%04hX) do not match flags from map (%04hX)",
|
||||
l->log.warning("(K-%zX) Set flags from client (%04hX) do not match set flags from map (%04hX)",
|
||||
z, flags, l->map->objects[z].set_flags);
|
||||
l->map->objects[z].set_flags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,8 +533,9 @@ static void on_sync_joining_player_compressed_state(shared_ptr<Client> c, uint8_
|
||||
for (size_t z = 0; z < min<size_t>(set_flags_header.num_enemy_sets, l->map->enemy_set_flags.size()); z++) {
|
||||
uint16_t flags = set_flags_r.get_u16l();
|
||||
if (flags != l->map->enemy_set_flags[z]) {
|
||||
l->log.warning("(S-%zX) Set flags from client (%04hX) do not match flags from map (%04hX)",
|
||||
l->log.warning("(S-%zX) Set flags from client (%04hX) do not match set flags from map (%04hX)",
|
||||
z, flags, l->map->enemy_set_flags[z]);
|
||||
l->map->enemy_set_flags[z] = flags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,32 +553,60 @@ static void on_sync_joining_player_compressed_state(shared_ptr<Client> c, uint8_
|
||||
}
|
||||
for (size_t z = 0; z < min<size_t>(num_event_flags, l->map->events.size()); z++) {
|
||||
uint16_t flags = event_set_flags_r.get_u16l();
|
||||
const auto& event = l->map->events[z];
|
||||
auto& event = l->map->events[z];
|
||||
if (flags != event.flags) {
|
||||
l->log.warning("(W-%02hhX-%" PRIX32 ") Event flags from client (%04hX) do not match flags from map (%04hX)",
|
||||
event.floor, event.event_id, flags, event.flags);
|
||||
event.flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t expected_unused_size = is_v1(c->version()) ? 0x200 : 0x240;
|
||||
size_t target_unused_size = is_v1(target->version()) ? 0x200 : 0x240;
|
||||
if (dec_header.unused_size != expected_unused_size) {
|
||||
l->log.warning("Unused data size (0x%" PRIX32 ") does not match expected size (0x%zX)",
|
||||
dec_header.unused_size.load(), expected_unused_size);
|
||||
size_t expected_switch_flag_num_floors = is_v1(c->version()) ? 0x10 : 0x12;
|
||||
size_t expected_switch_flags_size = expected_switch_flag_num_floors * 0x20;
|
||||
if (dec_header.switch_flags_size != expected_switch_flags_size) {
|
||||
l->log.warning("Switch flags size (0x%" PRIX32 ") does not match expected size (0x%zX)",
|
||||
dec_header.switch_flags_size.load(), expected_switch_flags_size);
|
||||
} else {
|
||||
l->log.info("Switch flags size matches expected size (0x%zX)", expected_switch_flags_size);
|
||||
}
|
||||
if (dec_header.unused_size != target_unused_size) {
|
||||
l->log.info("Resizing unused data from 0x%" PRIX32 " bytes to 0x%zX bytes",
|
||||
dec_header.unused_size.load(), target_unused_size);
|
||||
if (dec_header.unused_size >= decompressed.size()) {
|
||||
throw runtime_error("unused size is too large");
|
||||
if (l->switch_flags) {
|
||||
StringReader switch_flags_r = r.sub(r.where() + dec_header.entity_set_flags_size + dec_header.event_set_flags_size);
|
||||
for (size_t floor = 0; floor < expected_switch_flag_num_floors; floor++) {
|
||||
// There is a bug in most (perhaps all) versions of the game, which
|
||||
// causes this array to be too small. It looks like Sega forgot to
|
||||
// account for the header (G_SyncSetFlagState_6x6E_Decompressed)
|
||||
// before compressing the buffer, so the game cuts off the last 8
|
||||
// bytes of the switch flags. Since this only affects the last floor,
|
||||
// which rarely has any switches on it (or is even accessible by the
|
||||
// player), it's not surprising that no one noticed this. But it does
|
||||
// mean we have to check switch_flags_r.eof() here.
|
||||
for (size_t z = 0; (z < 0x20) && !switch_flags_r.eof(); z++) {
|
||||
uint8_t& l_flags = l->switch_flags->data[floor][z];
|
||||
uint8_t r_flags = switch_flags_r.get_u8();
|
||||
if (l_flags != r_flags) {
|
||||
l->log.warning("Switch flags do not match at %02zX[%02zX] (expected %02hhX, received %02hhX)",
|
||||
floor, z, l_flags, r_flags);
|
||||
l_flags = r_flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
decompressed.resize(decompressed.size() - dec_header.unused_size.load() + target_unused_size, '\0');
|
||||
auto* wdec_header = reinterpret_cast<G_SyncSetFlagState_6x6E_Decompressed*>(decompressed.data());
|
||||
wdec_header->unused_size = target_unused_size;
|
||||
wdec_header->total_size = wdec_header->entity_set_flags_size + wdec_header->event_set_flags_size + wdec_header->unused_size;
|
||||
}
|
||||
|
||||
// size_t target_switch_flag_num_floors = is_v1(target->version()) ? 0x10 : 0x12;
|
||||
// size_t target_switch_flags_size = target_switch_flag_num_floors * 0x20;
|
||||
// if (dec_header.switch_flags_size != target_switch_flags_size) {
|
||||
// l->log.info("Resizing switch flags from 0x%" PRIX32 " bytes to 0x%zX bytes",
|
||||
// dec_header.switch_flags_size.load(), target_switch_flags_size);
|
||||
// if (dec_header.switch_flags_size >= decompressed.size()) {
|
||||
// throw runtime_error("switch flags size is too large");
|
||||
// }
|
||||
// decompressed.resize(decompressed.size() - dec_header.switch_flags_size.load() + target_switch_flags_size, '\0');
|
||||
// auto* wdec_header = reinterpret_cast<G_SyncSetFlagState_6x6E_Decompressed*>(decompressed.data());
|
||||
// wdec_header->switch_flags_size = target_switch_flags_size;
|
||||
// wdec_header->total_size = wdec_header->entity_set_flags_size + wdec_header->event_set_flags_size + wdec_header->switch_flags_size;
|
||||
// }
|
||||
|
||||
send_game_join_sync_command_compressed(
|
||||
target,
|
||||
compressed_data,
|
||||
@@ -1514,6 +1543,14 @@ static void on_switch_state_changed(shared_ptr<Client> c, uint8_t command, uint8
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
|
||||
if (l->switch_flags) {
|
||||
if (cmd.flags & 1) {
|
||||
l->switch_flags->set(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
} else {
|
||||
l->switch_flags->clear(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd.flags && cmd.header.object_id != 0xFFFF) {
|
||||
if (!l->quest &&
|
||||
c->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) &&
|
||||
@@ -2718,25 +2755,130 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
|
||||
}
|
||||
}
|
||||
|
||||
static void on_set_entity_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();
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& cmd = check_size_t<G_SetEntityFlags_6x76>(data, size);
|
||||
const auto& cmd = check_size_t<G_SetEntitySetFlags_6x76>(data, size);
|
||||
if (l->map) {
|
||||
if (cmd.header.enemy_id >= 0x1000 && cmd.header.enemy_id < 0x4000) {
|
||||
if (cmd.header.enemy_id >= 0x4000) {
|
||||
uint16_t object_index = cmd.header.enemy_id - 0x4000;
|
||||
try {
|
||||
l->map->enemies.at(cmd.header.enemy_id - 0x1000).game_flags |= cmd.flags;
|
||||
uint16_t& set_flags = l->map->objects.at(object_index).set_flags;
|
||||
set_flags |= cmd.flags;
|
||||
l->log.info("Client set set flags %04hX on K-%hX (flags are now %04hX)",
|
||||
cmd.flags.load(), object_index, cmd.flags.load());
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Flag update refers to missing object");
|
||||
}
|
||||
|
||||
} else if (cmd.header.enemy_id >= 0x1000) {
|
||||
int32_t section = -1;
|
||||
int32_t wave_number = -1;
|
||||
uint16_t enemy_index = cmd.header.enemy_id - 0x1000;
|
||||
try {
|
||||
const auto& enemy = l->map->enemies.at(enemy_index);
|
||||
uint16_t& set_flags = l->map->enemy_set_flags.at(enemy.set_index);
|
||||
set_flags |= cmd.flags;
|
||||
section = enemy.section;
|
||||
wave_number = enemy.wave_number;
|
||||
l->log.info("Client set set flags %04hX on E-%hX (flags are now %04hX)",
|
||||
cmd.flags.load(), enemy_index, cmd.flags.load());
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Flag update refers to missing enemy");
|
||||
}
|
||||
} else if (cmd.header.enemy_id >= 0x4000) {
|
||||
try {
|
||||
l->map->objects.at(cmd.header.enemy_id - 0x4000).game_flags |= cmd.flags;
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Flag update refers to missing object");
|
||||
|
||||
if ((section >= 0) && (wave_number >= 0)) {
|
||||
// When all enemies in a wave event have (set_flags & 8), which means
|
||||
// they are defeated, set event_flags = (event_flags | 0x18) & (~4),
|
||||
// which means it is done and should not trigger
|
||||
bool all_enemies_defeated = true;
|
||||
l->log.info("Checking for defeated enemies with section=%04" PRIX32 " wave_number=%04" PRIX32,
|
||||
section, wave_number);
|
||||
for (const Map::Enemy* enemy : l->map->get_enemies(cmd.floor, section, wave_number)) {
|
||||
if (!(l->map->enemy_set_flags.at(enemy->set_index) & 8)) {
|
||||
l->log.info("E-%hX is not defeated; cannot advance event to finished state", enemy->enemy_id);
|
||||
all_enemies_defeated = false;
|
||||
break;
|
||||
} else {
|
||||
l->log.info("E-%hX is defeated", enemy->enemy_id);
|
||||
}
|
||||
}
|
||||
if (all_enemies_defeated) {
|
||||
l->log.info("All enemies defeated; setting events with section=%04" PRIX32 " wave_number=%04" PRIX32 " to finished state",
|
||||
section, wave_number);
|
||||
for (Map::Event* event : l->map->get_events(cmd.floor, section, wave_number)) {
|
||||
event->flags = (event->flags | 0x18) & (~4);
|
||||
l->log.info("Set flags on W-%02hhX-%" PRIX32 " to %04hX", event->floor, event->event_id, event->flags);
|
||||
|
||||
StringReader actions_r(l->map->event_action_stream);
|
||||
actions_r.go(event->action_stream_offset);
|
||||
while (!actions_r.eof()) {
|
||||
uint8_t opcode = actions_r.get_u8();
|
||||
switch (opcode) {
|
||||
case 0x00: // nop
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) nop", event->floor, event->event_id);
|
||||
break;
|
||||
case 0x01: // stop
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) stop", event->floor, event->event_id);
|
||||
actions_r.go(actions_r.size());
|
||||
break;
|
||||
case 0x08: { // construct_objects
|
||||
uint16_t section = actions_r.get_u16l();
|
||||
uint16_t group = actions_r.get_u16l();
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) construct_objects %04hX %04hX", event->floor, event->event_id, section, group);
|
||||
for (auto* obj : l->map->get_objects(event->floor, section, group)) {
|
||||
if (!(obj->set_flags & 0x0A)) {
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) Setting flags 0012 on object K-%hX", event->floor, event->event_id, obj->object_id);
|
||||
obj->set_flags |= 0x12;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x09: // construct_enemies
|
||||
case 0x0D: { // construct_enemies_stop
|
||||
uint16_t section = actions_r.get_u16l();
|
||||
uint16_t wave_number = actions_r.get_u16l();
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) construct_enemies %04hX %04hX", event->floor, event->event_id, section, wave_number);
|
||||
for (auto* enemy : l->map->get_enemies(event->floor, section, wave_number)) {
|
||||
uint16_t& set_flags = l->map->enemy_set_flags.at(enemy->set_index);
|
||||
if (!(set_flags & 0x0A)) {
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) Setting flags 0002 on enemy set S-%zX (from E-%hX)", event->floor, event->event_id, enemy->set_index, enemy->enemy_id);
|
||||
set_flags |= 0x02;
|
||||
}
|
||||
}
|
||||
if (opcode == 0x0D) {
|
||||
actions_r.go(actions_r.size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0A: // enable_switch_flag
|
||||
case 0x0B: { // disable_switch_flag
|
||||
// These opcodes cause the client to send 6x05 commands, so
|
||||
// we don't have to do anything here.
|
||||
uint16_t switch_flag_num = actions_r.get_u16l();
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) %sable_switch_flag %04hX",
|
||||
event->floor, event->event_id, (opcode & 1) ? "dis" : "en", switch_flag_num);
|
||||
break;
|
||||
}
|
||||
case 0x0C: { // trigger_event
|
||||
// This opcode causes the client to send a 6x67 command, so
|
||||
// we don't have to do anything here.
|
||||
uint32_t event_id = actions_r.get_u32l();
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) trigger_event W-%02hhX-%" PRIX32,
|
||||
event->floor, event->event_id, event->floor, event_id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
l->log.warning("(W-%02hhX-%" PRIX32 ") Invalid opcode %02hhX at offset %zX in event action stream",
|
||||
event->floor, event->event_id, opcode, actions_r.where() - 1);
|
||||
actions_r.go(actions_r.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2752,18 +2894,50 @@ static void on_trigger_set_event(shared_ptr<Client> c, uint8_t command, uint8_t
|
||||
|
||||
const auto& cmd = check_size_t<G_TriggerSetEvent_6x67>(data, size);
|
||||
if (l->map) {
|
||||
// TODO: The game's logic is significantly more complex than this. Do we
|
||||
// need to do anything fancy here?
|
||||
try {
|
||||
l->map->get_event(cmd.floor, cmd.event_id).flags |= 0x04;
|
||||
l->log.info("Client triggered set event W-%02" PRIX32 "-%" PRIX32, cmd.floor.load(), cmd.event_id.load());
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Client triggered missing event W-%02" PRIX32 "-%" PRIX32, cmd.floor.load(), cmd.event_id.load());
|
||||
l->log.warning("Client triggered missing set event W-%02" PRIX32 "-%" PRIX32, cmd.floor.load(), cmd.event_id.load());
|
||||
}
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_unknown_6x91(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_Unknown_6x91>(data, size);
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
if (l->switch_flags &&
|
||||
(cmd.should_set == 1) &&
|
||||
(cmd.switch_flag_num < 0x100) &&
|
||||
(cmd.switch_flag_floor < 0x12) &&
|
||||
(cmd.header.object_id >= 0x4000) &&
|
||||
(cmd.header.object_id != 0xFFFF)) {
|
||||
l->switch_flags->set(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
}
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_activate_timed_switch(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_ActivateTimedSwitch_6x93>(data, size);
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
if (l->switch_flags) {
|
||||
if (cmd.should_set == 1) {
|
||||
l->switch_flags->set(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
} else {
|
||||
l->switch_flags->clear(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
}
|
||||
}
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_battle_scores(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_BattleScores_6x7F<false>>(data, size);
|
||||
|
||||
@@ -4220,7 +4394,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6x73 */ {0x00, 0x00, 0x73, on_forward_check_game_quest},
|
||||
/* 6x74 */ {0x62, 0x69, 0x74, on_word_select, SDF::ALWAYS_FORWARD_TO_WATCHERS},
|
||||
/* 6x75 */ {0x00, 0x00, 0x75, on_set_quest_flag},
|
||||
/* 6x76 */ {0x00, 0x00, 0x76, on_set_entity_flag},
|
||||
/* 6x76 */ {0x00, 0x00, 0x76, on_set_entity_set_flag},
|
||||
/* 6x77 */ {0x00, 0x00, 0x77, on_forward_check_game},
|
||||
/* 6x78 */ {0x00, 0x00, 0x78, forward_subcommand_m},
|
||||
/* 6x79 */ {0x00, 0x00, 0x79, on_forward_check_lobby},
|
||||
@@ -4247,9 +4421,9 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6x8E */ {0x00, 0x00, 0x8E, on_forward_check_game},
|
||||
/* 6x8F */ {0x00, 0x00, 0x8F, on_forward_check_game},
|
||||
/* 6x90 */ {0x00, 0x00, 0x90, on_forward_check_game},
|
||||
/* 6x91 */ {0x00, 0x00, 0x91, on_forward_check_game},
|
||||
/* 6x91 */ {0x00, 0x00, 0x91, on_unknown_6x91},
|
||||
/* 6x92 */ {0x00, 0x00, 0x92, on_forward_check_game},
|
||||
/* 6x93 */ {0x00, 0x00, 0x93, on_forward_check_game},
|
||||
/* 6x93 */ {0x00, 0x00, 0x93, on_activate_timed_switch},
|
||||
/* 6x94 */ {0x00, 0x00, 0x94, on_warp},
|
||||
/* 6x95 */ {0x00, 0x00, 0x95, on_forward_check_game},
|
||||
/* 6x96 */ {0x00, 0x00, 0x96, on_forward_check_game},
|
||||
|
||||
+8
-3
@@ -2615,8 +2615,8 @@ void send_game_set_state(shared_ptr<Client> c) {
|
||||
G_SyncSetFlagState_6x6E_Decompressed header;
|
||||
header.entity_set_flags_size = sizeof(entity_set_flags_header) + (num_object_sets + num_enemy_sets) * sizeof(le_uint16_t);
|
||||
header.event_set_flags_size = sizeof(le_uint16_t) * l->map->events.size();
|
||||
header.unused_size = is_v1(c->version()) ? 0x200 : 0x240;
|
||||
header.total_size = header.entity_set_flags_size + header.event_set_flags_size + header.unused_size;
|
||||
header.switch_flags_size = is_v1(c->version()) ? 0x200 : 0x240;
|
||||
header.total_size = header.entity_set_flags_size + header.event_set_flags_size + header.switch_flags_size;
|
||||
|
||||
StringWriter w;
|
||||
w.put(header);
|
||||
@@ -2630,7 +2630,12 @@ void send_game_set_state(shared_ptr<Client> c) {
|
||||
for (const auto& event : l->map->events) {
|
||||
w.put_u16l(event.flags);
|
||||
}
|
||||
w.extend_by(header.unused_size, 0x00);
|
||||
if (l->switch_flags) {
|
||||
static_assert(sizeof(SwitchFlags) == 0x240, "switch_flags size is incorrect");
|
||||
w.write(l->switch_flags->data.data(), header.switch_flags_size);
|
||||
} else {
|
||||
w.extend_by(header.switch_flags_size, 0x00);
|
||||
}
|
||||
|
||||
send_game_join_sync_command(c, w.str(), 0x5F, 0x66, 0x6E);
|
||||
}
|
||||
|
||||
@@ -775,6 +775,19 @@ const char* name_for_floor(Episode episode, uint8_t floor) {
|
||||
}
|
||||
}
|
||||
|
||||
bool floor_is_boss_arena(Episode episode, uint8_t floor) {
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
return (floor >= 0x0B) && (floor <= 0x0E);
|
||||
case Episode::EP2:
|
||||
return (floor >= 0x0C) && (floor <= 0x0F);
|
||||
case Episode::EP4:
|
||||
return (floor == 0x09);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t class_flags_for_class(uint8_t char_class) {
|
||||
static constexpr uint8_t flags[12] = {
|
||||
0x25, 0x2A, 0x31, 0x45, 0x51, 0x52, 0x86, 0x89, 0x8A, 0x32, 0x85, 0x46};
|
||||
|
||||
@@ -76,6 +76,7 @@ extern const std::unordered_map<std::string, uint8_t> mag_color_for_name;
|
||||
size_t floor_limit_for_episode(Episode ep);
|
||||
uint8_t floor_for_name(const std::string& name);
|
||||
const char* name_for_floor(Episode episode, uint8_t floor);
|
||||
bool floor_is_boss_arena(Episode episode, uint8_t floor);
|
||||
|
||||
uint32_t class_flags_for_class(uint8_t char_class);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user