diff --git a/src/Map.cc b/src/Map.cc index 6c986ff0..349dbf20 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -668,7 +668,8 @@ Map::Enemy::Enemy( uint8_t floor, uint16_t section, uint16_t wave_number, - EnemyType type) + EnemyType type, + uint16_t alias_entity_id) : source_index(source_index), set_index(set_index), enemy_id(enemy_id), @@ -678,7 +679,8 @@ Map::Enemy::Enemy( wave_number(wave_number), type(type), floor(floor), - server_flags(0) {} + server_flags(0), + alias_entity_id(alias_entity_id) {} string Map::Enemy::str() const { return phosg::string_printf("[Map::Enemy E-%hX source %zX %s%s floor=%02hhX section=%04hX wave_number=%04hX server_flags=%02hhX]", @@ -800,9 +802,9 @@ void Map::add_enemy( size_t set_index = this->enemy_set_flags.size(); this->enemy_set_flags.emplace_back(0); - auto add = [&](EnemyType type) -> void { + auto add = [&](EnemyType type, uint16_t alias_enemy_id = 0xFFFF) -> void { uint16_t enemy_id = this->enemies.size(); - this->enemies.emplace_back(enemy_id, source_index, set_index, floor, e.section, e.wave_number, type); + this->enemies.emplace_back(enemy_id, source_index, set_index, floor, e.section, e.wave_number, type, alias_enemy_id); 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); }; @@ -1075,27 +1077,34 @@ void Map::add_enemy( case 0x00C5: // Unnamed subclass of TObjEnemyCustom add(EnemyType::VOL_OPT_2); break; - case 0x00C8: // TBoss4DarkFalz + case 0x00C8: { // TBoss4DarkFalz if ((e.num_children != 0) && (e.num_children != 0x200)) { this->log.warning("DARK_FALZ has an unusual num_children (0x%hX)", e.num_children.load()); } - default_num_children = -1; // Skip adding children (because we do it here) + uint16_t base_enemy_id = this->enemies.size(); if (difficulty) { add(EnemyType::DARK_FALZ_3); } else { add(EnemyType::DARK_FALZ_2); } + default_num_children = -1; // Skip adding children (because we do it here) for (size_t x = 0; x < 0x1FD; x++) { add(difficulty == 3 ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT); } - add(EnemyType::DARK_FALZ_3); - add(EnemyType::DARK_FALZ_2); - add(EnemyType::DARK_FALZ_1); + add(EnemyType::DARK_FALZ_3, base_enemy_id); + add(EnemyType::DARK_FALZ_2, base_enemy_id); + add(EnemyType::DARK_FALZ_1, base_enemy_id); break; - case 0x00CA: // TBoss6PlotFalz + } + case 0x00CA: { // TBoss6PlotFalz + uint16_t base_enemy_id = this->enemies.size(); add(EnemyType::OLGA_FLOW_2); - default_num_children = 0x200; + default_num_children = -1; // Skip adding children (because we do it here) + for (size_t x = 0; x < 0x200; x++) { + add(EnemyType::OLGA_FLOW_2, base_enemy_id); + } break; + } case 0x00CB: // TBoss7DeRolLeC add(EnemyType::BARBA_RAY); child_type = EnemyType::PIG_RAY; @@ -1653,12 +1662,34 @@ void Map::add_entities_from_quest_data( } } +const Map::Enemy& Map::find_enemy(uint16_t enemy_id) const { + return const_cast(this)->find_enemy(enemy_id); +} + +Map::Enemy& Map::find_enemy(uint16_t enemy_id) { + if (this->enemies.empty()) { + throw out_of_range("no enemies defined"); + } + if (enemy_id >= this->enemies.size()) { + throw out_of_range("enemy ID out of range"); + } + auto& enemy = this->enemies[enemy_id]; + if (enemy.alias_entity_id != 0xFFFF) { + if (enemy.alias_entity_id >= this->enemies.size()) { + throw out_of_range("aliased enemy ID out of range"); + } + return this->enemies[enemy.alias_entity_id]; + } else { + return enemy; + } +} + const Map::Enemy& Map::find_enemy(uint8_t floor, EnemyType type) const { return const_cast(this)->find_enemy(floor, type); } Map::Enemy& Map::find_enemy(uint8_t floor, EnemyType type) { - if (enemies.empty()) { + if (this->enemies.empty()) { throw out_of_range("no enemies defined"); } // TODO: Linear search is bad here. Do something better, like binary search diff --git a/src/Map.hh b/src/Map.hh index 8f3af559..24e09f96 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -249,6 +249,7 @@ struct Map { EnemyType type; uint8_t floor; uint8_t server_flags; + uint16_t alias_entity_id; Enemy( uint16_t enemy_id, @@ -257,7 +258,8 @@ struct Map { uint8_t floor, uint16_t section, uint16_t wave_number, - EnemyType type); + EnemyType type, + uint16_t alias_entity_id); std::string str() const; @@ -361,6 +363,8 @@ struct Map { size_t size, std::shared_ptr rare_rates = Map::DEFAULT_RARE_ENEMIES); + const Enemy& find_enemy(uint16_t enemy_id) const; + Enemy& find_enemy(uint16_t enemy_id); const Enemy& find_enemy(uint8_t floor, EnemyType type) const; Enemy& find_enemy(uint8_t floor, EnemyType type); std::vector get_objects(uint8_t floor, uint16_t section, uint16_t wave_number); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 91901d19..c1414d92 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -2604,7 +2604,7 @@ DropReconcileResult reconcile_drop_request_with_map( } else { if (map) { - map_enemy = &map->enemies.at(cmd.entity_id); + map_enemy = &map->find_enemy(cmd.entity_id); log.info("Drop check for E-%hX %s", map_enemy->enemy_id, phosg::name_for_enum(map_enemy->type)); res.effective_rt_index = rare_table_index_for_enemy_type(map_enemy->type); // rt_indexes in Episode 4 don't match those sent in the command; we just @@ -2837,10 +2837,12 @@ static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t fla if (boss_enemy_type != EnemyType::NONE) { l->log.info("Creating item from final boss (%s)", phosg::name_for_enum(boss_enemy_type)); uint16_t enemy_id = 0xFFFF; + uint8_t enemy_floor = 0xFF; if (l->map) { try { const auto& enemy = l->map->find_enemy(c->floor, boss_enemy_type); enemy_id = enemy.enemy_id; + enemy_floor = enemy.floor; if (c->floor != enemy.floor) { l->log.warning("Floor %02" PRIX32 " from client does not match entity\'s expected floor %02hhX", c->floor, enemy.floor); } @@ -2852,6 +2854,8 @@ static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t fla } if (boss_enemy_type != EnemyType::NONE) { + auto s = c->require_server_state(); + auto sdt = s->set_data_table(l->base_version, l->episode, l->mode, l->difficulty); G_StandardDropItemRequest_PC_V3_BB_6x60 drop_req = { { {0x60, 0x06, 0x0000}, @@ -2863,7 +2867,7 @@ static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t fla 2, 0, }, - 0x01, + sdt->default_area_for_floor(l->episode, enemy_floor), {}}; on_entity_drop_item_request(c, 0x62, l->leader_id, &drop_req, sizeof(drop_req)); } @@ -2925,13 +2929,13 @@ static void on_set_entity_set_flag(shared_ptr c, uint8_t command, uint8_ int32_t wave_number = -1; uint16_t enemy_index = cmd.header.enemy_id - 0x1000; try { - const auto& enemy = l->map->enemies.at(enemy_index); + const auto& enemy = l->map->find_enemy(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()); + cmd.flags.load(), enemy.enemy_id, cmd.flags.load()); } catch (const out_of_range&) { l->log.warning("Flag update refers to missing enemy"); } @@ -3207,11 +3211,11 @@ static void on_update_enemy_state(shared_ptr c, uint8_t command, uint8_t if (cmd.enemy_index >= l->map->enemies.size()) { return; } - auto& enemy = l->map->enemies[cmd.enemy_index]; + auto& enemy = l->map->find_enemy(cmd.enemy_index); enemy.game_flags = is_big_endian(c->version()) ? bswap32(cmd.flags) : cmd.flags.load(); enemy.total_damage = cmd.total_damage; enemy.set_last_hit_by_client_id(c->lobby_client_id); - l->log.info("E-%hX updated to damage=%hu game_flags=%08" PRIX32, cmd.enemy_index.load(), enemy.total_damage, enemy.game_flags); + l->log.info("E-%hX updated to damage=%hu game_flags=%08" PRIX32, enemy.enemy_id, enemy.total_damage, enemy.game_flags); } G_UpdateEnemyState_GC_6x0A sw_cmd = {{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.total_damage, cmd.flags.load()}; @@ -3376,7 +3380,7 @@ static void on_steal_exp_bb(shared_ptr c, uint8_t, uint8_t, void* data, return; } - const auto& enemy = l->map->enemies.at(cmd.enemy_index); + const auto& enemy = l->map->find_enemy(cmd.enemy_index); const auto& inventory = p->inventory; const auto& weapon = inventory.items[inventory.find_equipped_item(EquipSlot::WEAPON)]; @@ -3406,10 +3410,9 @@ static void on_steal_exp_bb(shared_ptr c, uint8_t, uint8_t, void* data, float ep2_factor = (l->episode == Episode::EP2) ? 1.3 : 1.0; uint32_t stolen_exp = max(min((enemy_exp * percent * ep2_factor) / 100.0f, (l->difficulty + 1) * 20), 1); if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { - c->log.info("Stolen EXP with bp_index=%" PRIX32 " enemy_exp=%" PRIu32 " percent=%g stolen_exp=%" PRIu32, - bp_index, enemy_exp, percent, stolen_exp); - send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", - stolen_exp, cmd.enemy_index.load(), phosg::name_for_enum(enemy.type)); + c->log.info("Stolen EXP from E-%hX with bp_index=%" PRIX32 " enemy_exp=%" PRIu32 " percent=%g stolen_exp=%" PRIu32, + enemy.enemy_id, bp_index, enemy_exp, percent, stolen_exp); + send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", stolen_exp, enemy.enemy_id, phosg::name_for_enum(enemy.type)); } add_player_exp(c, stolen_exp); } @@ -3434,9 +3437,9 @@ static void on_enemy_exp_request_bb(shared_ptr c, uint8_t, uint8_t, void throw runtime_error("client ID is too large"); } - auto& e = l->map->enemies[cmd.enemy_index]; + auto& e = l->map->find_enemy(cmd.enemy_index); string e_str = e.str(); - c->log.info("EXP requested for E-%hX => %s", cmd.enemy_index.load(), e_str.c_str()); + c->log.info("EXP requested for E-%hX => %s", e.enemy_id, e_str.c_str()); // If the requesting player never hit this enemy, they are probably cheating; // ignore the command. Also, each player sends a 6xC8 if they ever hit the @@ -3452,11 +3455,11 @@ static void on_enemy_exp_request_bb(shared_ptr c, uint8_t, uint8_t, void const auto& bp_table = s->battle_params->get_table(l->mode == GameMode::SOLO, l->episode); uint32_t bp_index = battle_param_index_for_enemy_type(l->episode, e.type); base_exp = bp_table.stats[l->difficulty][bp_index].experience; - } catch (const exception& e) { + } catch (const exception& exc) { if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { - send_text_message_printf(c, "$C5E-%hX __MISSING__\n%s", cmd.enemy_index.load(), e.what()); + send_text_message_printf(c, "$C5E-%hX __MISSING__\n%s", e.enemy_id, exc.what()); } else { - send_text_message_printf(c, "$C4Unknown enemy killed:\n%s", e.what()); + send_text_message_printf(c, "$C4Unknown enemy killed:\n%s", exc.what()); } } @@ -3499,7 +3502,7 @@ static void on_enemy_exp_request_bb(shared_ptr c, uint8_t, uint8_t, void send_text_message_printf( lc, "$C5+%" PRIu32 " E-%hX %s", player_exp, - cmd.enemy_index.load(), + e.enemy_id, phosg::name_for_enum(e.type)); } if (lc->character()->disp.stats.level < 199) {