diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index e10a63f7..00082183 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -2744,7 +2744,7 @@ struct C_SetChallengeModeDifficulty_BB_03DF { } __packed__; struct C_SetChallengeModeEXPMultiplier_BB_04DF { - le_float exp_multiplier = 0; + le_float exp_multiplier = 1.0f; } __packed__; struct C_SetChallengeRankText_BB_05DF { diff --git a/src/Lobby.cc b/src/Lobby.cc index 1caf55c2..ec0abedc 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -23,7 +23,8 @@ Lobby::Lobby(shared_ptr s, uint32_t id) episode(Episode::NONE), mode(GameMode::NORMAL), difficulty(0), - exp_multiplier(1.0f), + base_exp_multiplier(1), + challenge_exp_multiplier(1.0f), random_seed(random_object()), event(0), block(0), diff --git a/src/Lobby.hh b/src/Lobby.hh index d9a8e74b..dcff4404 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -82,7 +82,8 @@ struct Lobby : public std::enable_shared_from_this { Episode episode; GameMode mode; uint8_t difficulty; // 0-3 - float exp_multiplier; + uint16_t base_exp_multiplier; + float challenge_exp_multiplier; std::string password; std::string name; // This seed is also sent to the client for rare enemy generation diff --git a/src/Map.cc b/src/Map.cc index ca77b066..db41e598 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -62,45 +62,46 @@ void Map::add_enemy( Episode episode, uint8_t difficulty, uint8_t event, + uint8_t floor, size_t index, const EnemyEntry& e, const RareEnemyRates& rare_rates) { switch (e.base_type) { case 0x40: { bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.hildeblue); - this->add_enemy(e.floor, is_rare ? EnemyType::HILDEBLUE : EnemyType::HILDEBEAR); + this->add_enemy(floor, is_rare ? EnemyType::HILDEBLUE : EnemyType::HILDEBEAR); break; } case 0x41: { bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.rappy); switch (episode) { case Episode::EP1: - this->add_enemy(e.floor, is_rare ? EnemyType::AL_RAPPY : EnemyType::RAG_RAPPY); + this->add_enemy(floor, is_rare ? EnemyType::AL_RAPPY : EnemyType::RAG_RAPPY); break; case Episode::EP2: if (is_rare) { switch (event) { case 0x01: - this->add_enemy(e.floor, EnemyType::SAINT_RAPPY); + this->add_enemy(floor, EnemyType::SAINT_RAPPY); break; case 0x04: - this->add_enemy(e.floor, EnemyType::EGG_RAPPY); + this->add_enemy(floor, EnemyType::EGG_RAPPY); break; case 0x05: - this->add_enemy(e.floor, EnemyType::HALLO_RAPPY); + this->add_enemy(floor, EnemyType::HALLO_RAPPY); break; default: - this->add_enemy(e.floor, EnemyType::LOVE_RAPPY); + this->add_enemy(floor, EnemyType::LOVE_RAPPY); } } else { - this->add_enemy(e.floor, EnemyType::RAG_RAPPY); + this->add_enemy(floor, EnemyType::RAG_RAPPY); } break; case Episode::EP4: if (e.floor > 0x05) { - this->add_enemy(e.floor, is_rare ? EnemyType::DEL_RAPPY_ALT : EnemyType::SAND_RAPPY_ALT); + this->add_enemy(floor, is_rare ? EnemyType::DEL_RAPPY_ALT : EnemyType::SAND_RAPPY_ALT); } else { - this->add_enemy(e.floor, is_rare ? EnemyType::DEL_RAPPY : EnemyType::SAND_RAPPY); + this->add_enemy(floor, is_rare ? EnemyType::DEL_RAPPY : EnemyType::SAND_RAPPY); } break; default: @@ -109,292 +110,292 @@ void Map::add_enemy( break; } case 0x42: { - this->add_enemy(e.floor, EnemyType::MONEST); + this->add_enemy(floor, EnemyType::MONEST); for (size_t x = 0; x < 30; x++) { - this->add_enemy(e.floor, EnemyType::MOTHMANT); + this->add_enemy(floor, EnemyType::MOTHMANT); } break; } case 0x43: { - this->add_enemy(e.floor, e.fparam2 ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF); + this->add_enemy(floor, e.fparam2 ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF); break; } case 0x44: static const EnemyType types[3] = {EnemyType::BOOMA, EnemyType::GOBOOMA, EnemyType::GIGOBOOMA}; - this->add_enemy(e.floor, types[e.uparam1 % 3]); + this->add_enemy(floor, types[e.uparam1 % 3]); break; case 0x60: - this->add_enemy(e.floor, EnemyType::GRASS_ASSASSIN); + this->add_enemy(floor, EnemyType::GRASS_ASSASSIN); break; case 0x61: if ((episode == Episode::EP2) && (e.floor > 0x0F)) { - this->add_enemy(e.floor, EnemyType::DEL_LILY); + this->add_enemy(floor, EnemyType::DEL_LILY); } else { bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.nar_lily); - this->add_enemy(e.floor, is_rare ? EnemyType::NAR_LILY : EnemyType::POISON_LILY); + this->add_enemy(floor, is_rare ? EnemyType::NAR_LILY : EnemyType::POISON_LILY); } break; case 0x62: - this->add_enemy(e.floor, EnemyType::NANO_DRAGON); + this->add_enemy(floor, EnemyType::NANO_DRAGON); break; case 0x63: { static const EnemyType types[3] = {EnemyType::EVIL_SHARK, EnemyType::PAL_SHARK, EnemyType::GUIL_SHARK}; - this->add_enemy(e.floor, types[e.uparam1 % 3]); + this->add_enemy(floor, types[e.uparam1 % 3]); break; } case 0x64: { bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.pouilly_slime); for (size_t x = 0; x < 5; x++) { // Main slime + 4 clones - this->add_enemy(e.floor, is_rare ? EnemyType::POFUILLY_SLIME : EnemyType::POUILLY_SLIME); + this->add_enemy(floor, is_rare ? EnemyType::POFUILLY_SLIME : EnemyType::POUILLY_SLIME); } break; } case 0x65: - this->add_enemy(e.floor, EnemyType::PAN_ARMS); - this->add_enemy(e.floor, EnemyType::HIDOOM); - this->add_enemy(e.floor, EnemyType::MIGIUM); + this->add_enemy(floor, EnemyType::PAN_ARMS); + this->add_enemy(floor, EnemyType::HIDOOM); + this->add_enemy(floor, EnemyType::MIGIUM); break; case 0x80: - this->add_enemy(e.floor, (e.uparam1 & 0x01) ? EnemyType::GILLCHIC : EnemyType::DUBCHIC); + this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::GILLCHIC : EnemyType::DUBCHIC); break; case 0x81: - this->add_enemy(e.floor, EnemyType::GARANZ); + this->add_enemy(floor, EnemyType::GARANZ); break; case 0x82: { EnemyType type = e.fparam2 ? EnemyType::SINOW_GOLD : EnemyType::SINOW_BEAT; size_t count = (e.num_children == 0) ? 5 : (e.num_children + 1); for (size_t z = 0; z < count; z++) { - this->add_enemy(e.floor, type); + this->add_enemy(floor, type); } break; } case 0x83: - this->add_enemy(e.floor, EnemyType::CANADINE); + this->add_enemy(floor, EnemyType::CANADINE); break; case 0x84: - this->add_enemy(e.floor, EnemyType::CANANE); + this->add_enemy(floor, EnemyType::CANANE); for (size_t x = 0; x < 8; x++) { - this->add_enemy(e.floor, EnemyType::CANADINE_GROUP); + this->add_enemy(floor, EnemyType::CANADINE_GROUP); } break; case 0x85: - this->add_enemy(e.floor, EnemyType::DUBWITCH); + this->add_enemy(floor, EnemyType::DUBWITCH); break; case 0xA0: - this->add_enemy(e.floor, EnemyType::DELSABER); + this->add_enemy(floor, EnemyType::DELSABER); break; case 0xA1: - this->add_enemy(e.floor, EnemyType::CHAOS_SORCERER); - this->add_enemy(e.floor, EnemyType::BEE_R); - this->add_enemy(e.floor, EnemyType::BEE_L); + this->add_enemy(floor, EnemyType::CHAOS_SORCERER); + this->add_enemy(floor, EnemyType::BEE_R); + this->add_enemy(floor, EnemyType::BEE_L); break; case 0xA2: - this->add_enemy(e.floor, EnemyType::DARK_GUNNER); + this->add_enemy(floor, EnemyType::DARK_GUNNER); break; case 0xA3: - this->add_enemy(e.floor, EnemyType::DEATH_GUNNER); + this->add_enemy(floor, EnemyType::DEATH_GUNNER); break; case 0xA4: - this->add_enemy(e.floor, EnemyType::CHAOS_BRINGER); + this->add_enemy(floor, EnemyType::CHAOS_BRINGER); break; case 0xA5: - this->add_enemy(e.floor, EnemyType::DARK_BELRA); + this->add_enemy(floor, EnemyType::DARK_BELRA); break; case 0xA6: { static const EnemyType types[3] = {EnemyType::DIMENIAN, EnemyType::LA_DIMENIAN, EnemyType::SO_DIMENIAN}; - this->add_enemy(e.floor, types[e.uparam1 % 3]); + this->add_enemy(floor, types[e.uparam1 % 3]); break; } case 0xA7: - this->add_enemy(e.floor, EnemyType::BULCLAW); + this->add_enemy(floor, EnemyType::BULCLAW); for (size_t x = 0; x < 4; x++) { - this->add_enemy(e.floor, EnemyType::CLAW); + this->add_enemy(floor, EnemyType::CLAW); } break; case 0xA8: - this->add_enemy(e.floor, EnemyType::CLAW); + this->add_enemy(floor, EnemyType::CLAW); break; case 0xC0: if (episode == Episode::EP1) { - this->add_enemy(e.floor, EnemyType::DRAGON); + this->add_enemy(floor, EnemyType::DRAGON); } else if (episode == Episode::EP2) { - this->add_enemy(e.floor, EnemyType::GAL_GRYPHON); + this->add_enemy(floor, EnemyType::GAL_GRYPHON); } else { throw runtime_error("DRAGON-type enemy placed outside of Episodes 1 or 2"); } break; case 0xC1: - this->add_enemy(e.floor, EnemyType::DE_ROL_LE); + this->add_enemy(floor, EnemyType::DE_ROL_LE); for (size_t z = 0; z < 0x0A; z++) { - this->add_enemy(e.floor, EnemyType::DE_ROL_LE_BODY); + this->add_enemy(floor, EnemyType::DE_ROL_LE_BODY); } for (size_t z = 0; z < 0x09; z++) { - this->add_enemy(e.floor, EnemyType::DE_ROL_LE_MINE); + this->add_enemy(floor, EnemyType::DE_ROL_LE_MINE); } break; case 0xC2: - this->add_enemy(e.floor, EnemyType::VOL_OPT_1); + this->add_enemy(floor, EnemyType::VOL_OPT_1); for (size_t z = 0; z < 6; z++) { - this->add_enemy(e.floor, EnemyType::VOL_OPT_PILLAR); + this->add_enemy(floor, EnemyType::VOL_OPT_PILLAR); } for (size_t z = 0; z < 24; z++) { - this->add_enemy(e.floor, EnemyType::VOL_OPT_MONITOR); + this->add_enemy(floor, EnemyType::VOL_OPT_MONITOR); } for (size_t z = 0; z < 2; z++) { - this->add_enemy(e.floor, EnemyType::NONE); + this->add_enemy(floor, EnemyType::NONE); } - this->add_enemy(e.floor, EnemyType::VOL_OPT_AMP); - this->add_enemy(e.floor, EnemyType::VOL_OPT_CORE); - this->add_enemy(e.floor, EnemyType::NONE); + this->add_enemy(floor, EnemyType::VOL_OPT_AMP); + this->add_enemy(floor, EnemyType::VOL_OPT_CORE); + this->add_enemy(floor, EnemyType::NONE); break; case 0xC5: - this->add_enemy(e.floor, EnemyType::VOL_OPT_2); + this->add_enemy(floor, EnemyType::VOL_OPT_2); break; case 0xC8: if (difficulty) { - this->add_enemy(e.floor, EnemyType::DARK_FALZ_3); + this->add_enemy(floor, EnemyType::DARK_FALZ_3); } else { - this->add_enemy(e.floor, EnemyType::DARK_FALZ_2); + this->add_enemy(floor, EnemyType::DARK_FALZ_2); } for (size_t x = 0; x < 0x1FD; x++) { - this->add_enemy(e.floor, difficulty == 3 ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT); + this->add_enemy(floor, difficulty == 3 ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT); } - this->add_enemy(e.floor, EnemyType::DARK_FALZ_3); - this->add_enemy(e.floor, EnemyType::DARK_FALZ_2); - this->add_enemy(e.floor, EnemyType::DARK_FALZ_1); + this->add_enemy(floor, EnemyType::DARK_FALZ_3); + this->add_enemy(floor, EnemyType::DARK_FALZ_2); + this->add_enemy(floor, EnemyType::DARK_FALZ_1); break; case 0xCA: for (size_t z = 0; z < 0x201; z++) { - this->add_enemy(e.floor, EnemyType::OLGA_FLOW_2); + this->add_enemy(floor, EnemyType::OLGA_FLOW_2); } break; case 0xCB: - this->add_enemy(e.floor, EnemyType::BARBA_RAY); + this->add_enemy(floor, EnemyType::BARBA_RAY); for (size_t z = 0; z < 0x2F; z++) { - this->add_enemy(e.floor, EnemyType::PIG_RAY); + this->add_enemy(floor, EnemyType::PIG_RAY); } break; case 0xCC: for (size_t z = 0; z < 6; z++) { - this->add_enemy(e.floor, EnemyType::GOL_DRAGON); + this->add_enemy(floor, EnemyType::GOL_DRAGON); } break; case 0xD4: { EnemyType type = (e.uparam1 & 1) ? EnemyType::SINOW_SPIGELL : EnemyType::SINOW_BERILL; for (size_t z = 0; z < 5; z++) { - this->add_enemy(e.floor, type); + this->add_enemy(floor, type); } break; } case 0xD5: - this->add_enemy(e.floor, (e.uparam1 & 0x01) ? EnemyType::MERILTAS : EnemyType::MERILLIA); + this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::MERILTAS : EnemyType::MERILLIA); break; case 0xD6: if (e.uparam1 == 0) { - this->add_enemy(e.floor, EnemyType::MERICAROL); + this->add_enemy(floor, EnemyType::MERICAROL); } else { - this->add_enemy(e.floor, ((e.uparam1 % 3) == 2) ? EnemyType::MERICUS : EnemyType::MERIKLE); + this->add_enemy(floor, ((e.uparam1 % 3) == 2) ? EnemyType::MERICUS : EnemyType::MERIKLE); } break; case 0xD7: - this->add_enemy(e.floor, (e.uparam1 & 0x01) ? EnemyType::ZOL_GIBBON : EnemyType::UL_GIBBON); + this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::ZOL_GIBBON : EnemyType::UL_GIBBON); break; case 0xD8: - this->add_enemy(e.floor, EnemyType::GIBBLES); + this->add_enemy(floor, EnemyType::GIBBLES); break; case 0xD9: - this->add_enemy(e.floor, EnemyType::GEE); + this->add_enemy(floor, EnemyType::GEE); break; case 0xDA: - this->add_enemy(e.floor, EnemyType::GI_GUE); + this->add_enemy(floor, EnemyType::GI_GUE); break; case 0xDB: - this->add_enemy(e.floor, EnemyType::DELDEPTH); + this->add_enemy(floor, EnemyType::DELDEPTH); break; case 0xDC: - this->add_enemy(e.floor, EnemyType::DELBITER); + this->add_enemy(floor, EnemyType::DELBITER); break; case 0xDD: - this->add_enemy(e.floor, (e.uparam1 & 0x01) ? EnemyType::DOLMDARL : EnemyType::DOLMOLM); + this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::DOLMDARL : EnemyType::DOLMOLM); break; case 0xDE: - this->add_enemy(e.floor, EnemyType::MORFOS); + this->add_enemy(floor, EnemyType::MORFOS); break; case 0xDF: - this->add_enemy(e.floor, EnemyType::RECOBOX); + this->add_enemy(floor, EnemyType::RECOBOX); for (size_t x = 0; x < e.num_children; x++) { - this->add_enemy(e.floor, EnemyType::RECON); + this->add_enemy(floor, EnemyType::RECON); } break; case 0xE0: if ((episode == Episode::EP2) && (e.floor > 0x0F)) { - this->add_enemy(e.floor, EnemyType::EPSILON); + this->add_enemy(floor, EnemyType::EPSILON); for (size_t z = 0; z < 4; z++) { - this->add_enemy(e.floor, EnemyType::EPSIGUARD); + this->add_enemy(floor, EnemyType::EPSIGUARD); } } else { - this->add_enemy(e.floor, (e.uparam1 & 0x01) ? EnemyType::SINOW_ZELE : EnemyType::SINOW_ZOA); + this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::SINOW_ZELE : EnemyType::SINOW_ZOA); } break; case 0xE1: - this->add_enemy(e.floor, EnemyType::ILL_GILL); + this->add_enemy(floor, EnemyType::ILL_GILL); break; case 0x0110: - this->add_enemy(e.floor, EnemyType::ASTARK); + this->add_enemy(floor, EnemyType::ASTARK); break; case 0x0111: if (e.floor > 0x05) { - this->add_enemy(e.floor, e.fparam2 ? EnemyType::YOWIE_ALT : EnemyType::SATELLITE_LIZARD_ALT); + this->add_enemy(floor, e.fparam2 ? EnemyType::YOWIE_ALT : EnemyType::SATELLITE_LIZARD_ALT); } else { - this->add_enemy(e.floor, e.fparam2 ? EnemyType::YOWIE : EnemyType::SATELLITE_LIZARD); + this->add_enemy(floor, e.fparam2 ? EnemyType::YOWIE : EnemyType::SATELLITE_LIZARD); } break; case 0x0112: { bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.merissa_aa); - this->add_enemy(e.floor, is_rare ? EnemyType::MERISSA_AA : EnemyType::MERISSA_A); + this->add_enemy(floor, is_rare ? EnemyType::MERISSA_AA : EnemyType::MERISSA_A); break; } case 0x0113: - this->add_enemy(e.floor, EnemyType::GIRTABLULU); + this->add_enemy(floor, EnemyType::GIRTABLULU); break; case 0x0114: { bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.pazuzu); if (e.floor > 0x05) { - this->add_enemy(e.floor, is_rare ? EnemyType::PAZUZU_ALT : EnemyType::ZU_ALT); + this->add_enemy(floor, is_rare ? EnemyType::PAZUZU_ALT : EnemyType::ZU_ALT); } else { - this->add_enemy(e.floor, is_rare ? EnemyType::PAZUZU : EnemyType::ZU); + this->add_enemy(floor, is_rare ? EnemyType::PAZUZU : EnemyType::ZU); } break; } case 0x0115: if (e.uparam1 & 2) { - this->add_enemy(e.floor, EnemyType::BA_BOOTA); + this->add_enemy(floor, EnemyType::BA_BOOTA); } else { - this->add_enemy(e.floor, (e.uparam1 & 1) ? EnemyType::ZE_BOOTA : EnemyType::BOOTA); + this->add_enemy(floor, (e.uparam1 & 1) ? EnemyType::ZE_BOOTA : EnemyType::BOOTA); } break; case 0x0116: { bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.dorphon_eclair); - this->add_enemy(e.floor, is_rare ? EnemyType::DORPHON_ECLAIR : EnemyType::DORPHON); + this->add_enemy(floor, is_rare ? EnemyType::DORPHON_ECLAIR : EnemyType::DORPHON); break; } case 0x0117: { static const EnemyType types[3] = {EnemyType::GORAN, EnemyType::PYRO_GORAN, EnemyType::GORAN_DETONATOR}; - this->add_enemy(e.floor, types[e.uparam1 % 3]); + this->add_enemy(floor, types[e.uparam1 % 3]); break; } case 0x0119: { bool is_rare = this->check_and_log_rare_enemy((e.fparam2 != 0.0f), rare_rates.kondrieu); if (is_rare) { - this->add_enemy(e.floor, EnemyType::KONDRIEU); + this->add_enemy(floor, EnemyType::KONDRIEU); } else { - this->add_enemy(e.floor, (e.uparam1 & 1) ? EnemyType::SHAMBERTIN : EnemyType::SAINT_MILLION); + this->add_enemy(floor, (e.uparam1 & 1) ? EnemyType::SHAMBERTIN : EnemyType::SAINT_MILLION); } break; } default: for (size_t z = 0; z < static_cast(e.num_children + 1); z++) { - this->add_enemy(e.floor, EnemyType::UNKNOWN); + this->add_enemy(floor, EnemyType::UNKNOWN); } static_game_data_log.warning( "(Entry %zu, offset %zX in file) Unknown enemy type %04hX", @@ -407,6 +408,7 @@ void Map::add_enemies_from_map_data( Episode episode, uint8_t difficulty, uint8_t event, + uint8_t floor, const void* data, size_t size, const RareEnemyRates& rare_rates) { @@ -417,7 +419,7 @@ void Map::add_enemies_from_map_data( StringReader r(data, size); for (size_t y = 0; y < entry_count; y++) { - this->add_enemy(episode, difficulty, event, y, r.get(), rare_rates); + this->add_enemy(episode, difficulty, event, floor, y, r.get(), rare_rates); } } @@ -500,6 +502,7 @@ void Map::add_random_enemies_from_map_data( Episode episode, uint8_t difficulty, uint8_t event, + uint8_t floor, StringReader wave_events_segment_r, StringReader locations_segment_r, StringReader definitions_segment_r, @@ -579,6 +582,7 @@ void Map::add_random_enemies_from_map_data( e.base_type = rand_enemy_base_types.at(weight_entry.base_type_index); e.wave_number = wave_number; e.section = entry.section; + e.floor = floor; size_t bs_min = 0; size_t bs_max = definitions_header.entry_count - 1; @@ -619,7 +623,7 @@ void Map::add_random_enemies_from_map_data( enemy_log.info("Creating enemy with base_type %04hX fparam2 %g uparam1 %04hX", e.base_type.load(), e.fparam2.load(), e.uparam1.load()); // Trace: at 0080E6FE CX is base_type - this->add_enemy(episode, difficulty, event, 0, e, rare_rates); + this->add_enemy(episode, difficulty, event, floor, 0, e, rare_rates); } else { enemy_log.info("Cannot create enemy: parameters are missing"); } @@ -743,6 +747,7 @@ void Map::add_enemies_and_objects_from_quest_data( episode, difficulty, event, + floor, r.pgetv(sections.enemies + sizeof(header), header.data_size), header.data_size, rare_rates); @@ -758,6 +763,7 @@ void Map::add_enemies_and_objects_from_quest_data( episode, difficulty, event, + floor, r.sub(sections.wave_events + sizeof(SectionHeader), wave_events_header.data_size), r.sub(sections.random_enemy_locations + sizeof(SectionHeader), random_enemy_locations_header.data_size), r.sub(sections.random_enemy_definitions + sizeof(SectionHeader), random_enemy_definitions_header.data_size), diff --git a/src/Map.hh b/src/Map.hh index 8f4923e1..c3d7c971 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -222,6 +222,7 @@ struct Map { Episode episode, uint8_t difficulty, uint8_t event, + uint8_t floor, size_t index, const EnemyEntry& e, const RareEnemyRates& rare_rates = Map::DEFAULT_RARE_ENEMIES); @@ -229,6 +230,7 @@ struct Map { Episode episode, uint8_t difficulty, uint8_t event, + uint8_t floor, const void* data, size_t size, const RareEnemyRates& rare_rates = Map::DEFAULT_RARE_ENEMIES); @@ -236,6 +238,7 @@ struct Map { Episode episode, uint8_t difficulty, uint8_t event, + uint8_t floor, StringReader wave_events_r, StringReader random_enemy_locations_r, StringReader random_enemy_definitions_r, diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index f86c5e23..b28b26b3 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3342,8 +3342,8 @@ static void on_DF_BB(shared_ptr c, uint16_t command, uint32_t, string& d case 0x04DF: { const auto& cmd = check_size_t(data); - l->exp_multiplier = cmd.exp_multiplier; - l->log.info("(Challenge mode) EXP multiplier set to %g", l->exp_multiplier); + l->challenge_exp_multiplier = cmd.exp_multiplier; + l->log.info("(Challenge mode) EXP multiplier set to %g", l->challenge_exp_multiplier); break; } @@ -3740,7 +3740,7 @@ shared_ptr create_game_generic( auto map_data = s->load_bb_file(filename, "", "map/" + filename); size_t start_offset = game->map->enemies.size(); game->map->add_enemies_from_map_data( - game->episode, game->difficulty, game->event, map_data->data(), map_data->size()); + game->episode, game->difficulty, game->event, floor, map_data->data(), map_data->size()); size_t entries_loaded = game->map->enemies.size() - start_offset; c->log.info("[Map/%zu:e] Loaded %s (%zu entries)", floor, filename.c_str(), entries_loaded); @@ -3916,7 +3916,7 @@ static void on_6F(shared_ptr c, uint16_t command, uint32_t, string& data c->config.clear_flag(Client::Flag::LOADING); send_resume_game(l, c); - if ((l->base_version == GameVersion::BB) && (l->mode != GameMode::CHALLENGE)) { + if (l->base_version == GameVersion::BB) { send_set_exp_multiplier(l); } send_server_time(c); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 96ee41e2..16ebcb34 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1790,11 +1790,11 @@ static void on_enemy_killed_bb(shared_ptr c, uint8_t, uint8_t, const voi return; } - uint32_t experience = 0xFFFFFFFF; + double experience = 0.0; try { 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); - experience = bp_table.stats[l->difficulty][bp_index].experience * l->exp_multiplier; + experience = bp_table.stats[l->difficulty][bp_index].experience; } catch (const exception& e) { 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()); @@ -1804,28 +1804,43 @@ static void on_enemy_killed_bb(shared_ptr c, uint8_t, uint8_t, const voi } e.flags |= Map::Enemy::Flag::DEFEATED; - for (size_t x = 0; x < l->max_clients; x++) { - if (!((e.flags >> x) & 1)) { - continue; // Player did not hit this enemy - } - auto other_c = l->clients[x]; - if (!other_c) { - continue; // No player - } - if (other_c->floor != e.floor) { - continue; - } - - if (experience != 0xFFFFFFFF) { - // Killer gets full experience, others get 77% - bool is_killer = (e.last_hit_by_client_id == other_c->lobby_client_id); - uint32_t player_exp = is_killer ? experience : ((experience * 77) / 100); - if (other_c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { - send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", - player_exp, cmd.enemy_index.load(), name_for_enum(e.type)); + if (experience != 0.0) { + for (size_t x = 0; x < l->max_clients; x++) { + auto lc = l->clients[x]; + if (!lc) { + continue; } - if (other_c->game_data.character()->disp.stats.level < 199) { - add_player_exp(other_c, player_exp); + if (!((e.flags >> x) & 1)) { + if (lc->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + send_text_message_printf(lc, "$C5E-%hX %s\n$C4NOHIT", cmd.enemy_index.load(), name_for_enum(e.type)); + } + continue; // Player did not hit this enemy + } + if (lc->floor != e.floor) { + if (lc->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + send_text_message_printf(lc, "$C5E-%hX %s\n$C4FLOOR Y:%02" PRIX32 " E:%02hhX", + cmd.enemy_index.load(), name_for_enum(e.type), lc->floor, e.floor); + } + continue; + } + + // In PSOBB, Sega decided to add a 30% EXP boost for Episode 2. They could + // have done something reasonable, like edit the BattleParamEntry files so + // the monsters would all give more EXP, but they did something far lazier + // instead: they just stuck an if statement in the client's EXP request + // function. We, unfortunately, have to do the same here. + bool is_killer = (e.last_hit_by_client_id == lc->lobby_client_id); + bool is_ep2 = (l->episode == Episode::EP2); + uint32_t player_exp = experience * + (is_killer ? 1.0 : 0.8) * + l->base_exp_multiplier * + l->challenge_exp_multiplier * + (is_ep2 ? 1.3 : 1.0); + if (lc->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + send_text_message_printf(lc, "$C5+%" PRIu32 " E-%hX %s", player_exp, cmd.enemy_index.load(), name_for_enum(e.type)); + } + if (lc->game_data.character()->disp.stats.level < 199) { + add_player_exp(lc, player_exp); } } } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 138fff3c..9e6d7b6a 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2287,10 +2287,7 @@ void send_set_exp_multiplier(std::shared_ptr l) { if (!l->is_game()) { throw logic_error("6xDD can only be sent in games (not in lobbies)"); } - if (floorf(l->exp_multiplier) != l->exp_multiplier) { - throw runtime_error("EXP multiplier is not an integer"); - } - G_SetEXPMultiplier_BB_6xDD cmd = {{0xDD, sizeof(G_SetEXPMultiplier_BB_6xDD) / 4, static_cast(l->exp_multiplier)}}; + G_SetEXPMultiplier_BB_6xDD cmd = {{0xDD, sizeof(G_SetEXPMultiplier_BB_6xDD) / 4, (l->mode == GameMode::CHALLENGE) ? 1 : l->base_exp_multiplier}}; send_command_t(l, 0x60, 0x00, cmd); }