load maps on all versions
This commit is contained in:
+5
-26
@@ -52,19 +52,6 @@ void ItemCreator::set_random_state(uint32_t seed, uint32_t absolute_offset) {
|
||||
}
|
||||
}
|
||||
|
||||
void ItemCreator::set_box_destroyed(uint16_t entity_id) {
|
||||
this->destroyed_boxes.emplace(entity_id);
|
||||
}
|
||||
|
||||
void ItemCreator::set_monster_destroyed(uint16_t entity_id) {
|
||||
this->destroyed_monsters.emplace(entity_id);
|
||||
}
|
||||
|
||||
void ItemCreator::clear_destroyed_entities() {
|
||||
this->destroyed_monsters.clear();
|
||||
this->destroyed_boxes.clear();
|
||||
}
|
||||
|
||||
bool ItemCreator::are_rare_drops_allowed() const {
|
||||
// Note: The client has an additional check here, which appears to be a subtle
|
||||
// anti-cheating measure. There is a flag on the client, initially zero, which
|
||||
@@ -134,16 +121,12 @@ uint8_t ItemCreator::normalize_area_number(uint8_t area) const {
|
||||
}
|
||||
}
|
||||
|
||||
ItemCreator::DropResult ItemCreator::on_box_item_drop(uint16_t entity_id, uint8_t area) {
|
||||
return this->destroyed_boxes.count(entity_id)
|
||||
? DropResult()
|
||||
: this->on_box_item_drop_with_area_norm(this->normalize_area_number(area));
|
||||
ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area) {
|
||||
return this->on_box_item_drop_with_area_norm(this->normalize_area_number(area));
|
||||
}
|
||||
|
||||
ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint16_t entity_id, uint32_t enemy_type, uint8_t area) {
|
||||
return this->destroyed_monsters.count(entity_id)
|
||||
? DropResult()
|
||||
: this->on_monster_item_drop_with_area_norm(enemy_type, this->normalize_area_number(area));
|
||||
ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t enemy_type, uint8_t area) {
|
||||
return this->on_monster_item_drop_with_area_norm(enemy_type, this->normalize_area_number(area));
|
||||
}
|
||||
|
||||
ItemCreator::DropResult ItemCreator::on_box_item_drop_with_area_norm(uint8_t area_norm) {
|
||||
@@ -1680,11 +1663,7 @@ void ItemCreator::generate_weapon_shop_item_bonus2(ItemData& item, size_t player
|
||||
}
|
||||
|
||||
ItemCreator::DropResult ItemCreator::on_specialized_box_item_drop(
|
||||
uint16_t entity_id, uint8_t area, float def_z, uint32_t def0, uint32_t def1, uint32_t def2) {
|
||||
if (this->destroyed_boxes.count(entity_id)) {
|
||||
return DropResult();
|
||||
}
|
||||
|
||||
uint8_t area, float def_z, uint32_t def0, uint32_t def1, uint32_t def2) {
|
||||
DropResult res;
|
||||
res.item = this->base_item_for_specialized_box(def0, def1, def2);
|
||||
if (def_z == 0.0f) {
|
||||
|
||||
+3
-9
@@ -29,19 +29,15 @@ public:
|
||||
~ItemCreator() = default;
|
||||
|
||||
void set_random_state(uint32_t seed, uint32_t absolute_offset);
|
||||
void clear_destroyed_entities();
|
||||
|
||||
struct DropResult {
|
||||
ItemData item;
|
||||
bool is_from_rare_table = false;
|
||||
};
|
||||
|
||||
DropResult on_monster_item_drop(uint16_t entity_id, uint32_t enemy_type, uint8_t area);
|
||||
DropResult on_box_item_drop(uint16_t entity_id, uint8_t area);
|
||||
DropResult on_specialized_box_item_drop(uint16_t entity_id, uint8_t area, float def_z, uint32_t def0, uint32_t def1, uint32_t def2);
|
||||
|
||||
void set_monster_destroyed(uint16_t entity_id);
|
||||
void set_box_destroyed(uint16_t entity_id);
|
||||
DropResult on_monster_item_drop(uint32_t enemy_type, uint8_t area);
|
||||
DropResult on_box_item_drop(uint8_t area);
|
||||
DropResult on_specialized_box_item_drop(uint8_t area, float def_z, uint32_t def0, uint32_t def1, uint32_t def2);
|
||||
|
||||
ItemData base_item_for_specialized_box(uint32_t def0, uint32_t def1, uint32_t def2) const;
|
||||
|
||||
@@ -82,8 +78,6 @@ private:
|
||||
// Note: The original implementation uses 17 different random states for some
|
||||
// reason. We forego that and use only one for simplicity.
|
||||
PSOV2Encryption random_crypt;
|
||||
std::unordered_set<uint16_t> destroyed_monsters;
|
||||
std::unordered_set<uint16_t> destroyed_boxes;
|
||||
|
||||
inline bool is_v3() const {
|
||||
return !is_v1_or_v2(this->version);
|
||||
|
||||
+48
-19
@@ -262,16 +262,20 @@ void Lobby::create_item_creator() {
|
||||
}
|
||||
|
||||
void Lobby::load_maps() {
|
||||
auto s = this->require_server_state();
|
||||
this->map = make_shared<Map>(this->lobby_id, this->random_crypt);
|
||||
|
||||
auto rare_rates = ((this->base_version == Version::BB_V4) && this->rare_enemy_rates)
|
||||
? this->rare_enemy_rates
|
||||
: Map::DEFAULT_RARE_ENEMIES;
|
||||
|
||||
auto s = this->require_server_state();
|
||||
if (this->quest) {
|
||||
auto leader_c = this->clients.at(this->leader_id);
|
||||
if (!leader_c) {
|
||||
throw logic_error("lobby leader is missing");
|
||||
}
|
||||
|
||||
auto vq = this->quest->version(Version::BB_V4, leader_c->language());
|
||||
auto vq = this->quest->version(this->base_version, leader_c->language());
|
||||
auto dat_contents = prs_decompress(*vq->dat_contents);
|
||||
this->map->clear();
|
||||
this->map->add_enemies_and_objects_from_quest_data(
|
||||
@@ -281,16 +285,48 @@ void Lobby::load_maps() {
|
||||
dat_contents.data(),
|
||||
dat_contents.size(),
|
||||
this->random_seed,
|
||||
this->rare_enemy_rates ? this->rare_enemy_rates : Map::NO_RARE_ENEMIES);
|
||||
rare_rates);
|
||||
|
||||
} else if (this->mode != GameMode::CHALLENGE) {
|
||||
// No quest loaded - load free-roam maps instead. Don't load free-roam maps
|
||||
// in Challenge mode, since players can't go to Ragol without a quest loaded
|
||||
|
||||
} else { // No quest loaded
|
||||
for (size_t floor = 0; floor < 0x10; floor++) {
|
||||
this->log.info("[Map/%zu] Using variations %" PRIX32 ", %" PRIX32,
|
||||
floor, this->variations[floor * 2].load(), this->variations[floor * 2 + 1].load());
|
||||
|
||||
auto get_file_data = [&](const string& filename) -> shared_ptr<const string> {
|
||||
if (this->base_version == Version::BB_V4) {
|
||||
try {
|
||||
return s->load_bb_file(filename);
|
||||
} catch (const exception& e) {
|
||||
this->log.info("[Map/%zu:e] Failed to load %s from BB patch tree: %s", floor, filename.c_str(), e.what());
|
||||
}
|
||||
} else if (this->base_version == Version::PC_V2) {
|
||||
try {
|
||||
string path = "system/patch-pc/Media/PSO/" + filename;
|
||||
auto ret = make_shared<string>(load_file(path));
|
||||
this->log.info("[Map/%zu:e] Loaded %s from PC patch tree", floor, filename.c_str());
|
||||
return ret;
|
||||
} catch (const exception& e) {
|
||||
this->log.info("[Map/%zu:e] Failed to load %s from PC patch tree: %s", floor, filename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
try {
|
||||
string path = string_printf("system/maps/%s/%s", file_path_token_for_version(this->base_version), filename.c_str());
|
||||
auto ret = make_shared<string>(load_file(path));
|
||||
this->log.info("[Map/%zu:e] Loaded %s from default maps", floor, filename.c_str());
|
||||
return ret;
|
||||
} catch (const exception& e) {
|
||||
this->log.info("[Map/%zu:e] Failed to load %s from PC patch tree: %s", floor, filename.c_str(), e.what());
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
auto enemy_filenames = map_filenames_for_variation(
|
||||
this->base_version,
|
||||
this->episode,
|
||||
(this->mode == GameMode::SOLO),
|
||||
this->mode,
|
||||
floor,
|
||||
this->variations[floor * 2],
|
||||
this->variations[floor * 2 + 1],
|
||||
@@ -300,8 +336,8 @@ void Lobby::load_maps() {
|
||||
} else {
|
||||
bool any_map_loaded = false;
|
||||
for (const string& filename : enemy_filenames) {
|
||||
try {
|
||||
auto map_data = s->load_bb_file(filename, "", "map/" + filename);
|
||||
auto map_data = get_file_data(filename);
|
||||
if (map_data) {
|
||||
this->map->add_enemies_from_map_data(
|
||||
this->episode,
|
||||
this->difficulty,
|
||||
@@ -309,11 +345,9 @@ void Lobby::load_maps() {
|
||||
floor,
|
||||
map_data->data(),
|
||||
map_data->size(),
|
||||
this->rare_enemy_rates);
|
||||
rare_rates);
|
||||
any_map_loaded = true;
|
||||
break;
|
||||
} catch (const exception& e) {
|
||||
this->log.info("[Map/%zu:e] Failed to load %s: %s", floor, filename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
if (!any_map_loaded) {
|
||||
@@ -322,8 +356,9 @@ void Lobby::load_maps() {
|
||||
}
|
||||
|
||||
auto object_filenames = map_filenames_for_variation(
|
||||
this->base_version,
|
||||
this->episode,
|
||||
(this->mode == GameMode::SOLO),
|
||||
this->mode,
|
||||
floor,
|
||||
this->variations[floor * 2],
|
||||
this->variations[floor * 2 + 1],
|
||||
@@ -333,13 +368,11 @@ void Lobby::load_maps() {
|
||||
} else {
|
||||
bool any_map_loaded = false;
|
||||
for (const string& filename : object_filenames) {
|
||||
try {
|
||||
auto map_data = s->load_bb_file(filename, "", "map/" + filename);
|
||||
auto map_data = get_file_data(filename);
|
||||
if (map_data) {
|
||||
this->map->add_objects_from_map_data(floor, map_data->data(), map_data->size());
|
||||
any_map_loaded = true;
|
||||
break;
|
||||
} catch (const exception& e) {
|
||||
this->log.info("[Map/%zu:o] Failed to load %s: %s", floor, filename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
if (!any_map_loaded) {
|
||||
@@ -361,10 +394,6 @@ void Lobby::load_maps() {
|
||||
}
|
||||
this->log.info("Loaded maps contain %zu object entries and %zu enemy entries overall (%zu as rares)",
|
||||
this->map->objects.size(), this->map->enemies.size(), this->map->rare_enemy_indexes.size());
|
||||
|
||||
if (this->item_creator) {
|
||||
this->item_creator->clear_destroyed_entities();
|
||||
}
|
||||
}
|
||||
|
||||
void Lobby::create_ep3_server() {
|
||||
|
||||
+687
-68
@@ -14,6 +14,561 @@ using namespace std;
|
||||
|
||||
static constexpr float UINT32_MAX_AS_FLOAT = 4294967296.0f;
|
||||
|
||||
const char* Map::name_for_object_type(uint16_t type) {
|
||||
switch (type) {
|
||||
case 0x0000:
|
||||
return "TObjPlayerSet";
|
||||
case 0x0001:
|
||||
return "TObjParticle";
|
||||
case 0x0002:
|
||||
return "TObjAreaWarpForest";
|
||||
case 0x0003:
|
||||
return "TObjMapWarpForest";
|
||||
case 0x0004:
|
||||
return "TObjLight";
|
||||
case 0x0006:
|
||||
return "TObjEnvSound";
|
||||
case 0x0007:
|
||||
return "TObjFogCollision";
|
||||
case 0x0008:
|
||||
return "TObjEvtCollision";
|
||||
case 0x0009:
|
||||
return "TObjCollision";
|
||||
case 0x000A:
|
||||
return "TOMineIcon01";
|
||||
case 0x000B:
|
||||
return "TOMineIcon02";
|
||||
case 0x000C:
|
||||
return "TOMineIcon03";
|
||||
case 0x000D:
|
||||
return "TOMineIcon04";
|
||||
case 0x000E:
|
||||
return "TObjRoomId";
|
||||
case 0x000F:
|
||||
return "TOSensorGeneral01";
|
||||
case 0x0011:
|
||||
return "TEF_LensFlare";
|
||||
case 0x0012:
|
||||
return "TObjQuestCol";
|
||||
case 0x0013:
|
||||
return "TOHealGeneral";
|
||||
case 0x0014:
|
||||
return "TObjMapCsn";
|
||||
case 0x0015:
|
||||
return "TObjQuestColA";
|
||||
case 0x0016:
|
||||
return "TObjItemLight";
|
||||
case 0x0017:
|
||||
return "TObjRaderCol";
|
||||
case 0x0018:
|
||||
return "TObjFogCollisionSwitch";
|
||||
case 0x0019:
|
||||
return "TObjWarpBossMulti(off)/TObjWarpBoss(on)";
|
||||
case 0x001A:
|
||||
return "TObjSinBoard";
|
||||
case 0x001B:
|
||||
return "TObjAreaWarpQuest";
|
||||
case 0x001C:
|
||||
return "TObjAreaWarpEnding";
|
||||
case 0x001D:
|
||||
return "__UNNAMED_001D__";
|
||||
case 0x001E:
|
||||
return "__UNNAMED_001E__";
|
||||
case 0x001F:
|
||||
return "TObjRaderHideCol";
|
||||
case 0x0020:
|
||||
return "TOSwitchItem";
|
||||
case 0x0021:
|
||||
return "TOSymbolchatColli";
|
||||
case 0x0022:
|
||||
return "TOKeyCol";
|
||||
case 0x0023:
|
||||
return "TOAttackableCol";
|
||||
case 0x0024:
|
||||
return "TOSwitchAttack";
|
||||
case 0x0025:
|
||||
return "TOSwitchTimer";
|
||||
case 0x0026:
|
||||
return "TOChatSensor";
|
||||
case 0x0027:
|
||||
return "TObjRaderIcon";
|
||||
case 0x0028:
|
||||
return "TObjEnvSoundEx";
|
||||
case 0x0029:
|
||||
return "TObjEnvSoundGlobal";
|
||||
case 0x0040:
|
||||
return "TShopGenerator";
|
||||
case 0x0041:
|
||||
return "TObjLuker";
|
||||
case 0x0042:
|
||||
return "TObjBgmCol";
|
||||
case 0x0043:
|
||||
return "TObjCityMainWarp";
|
||||
case 0x0044:
|
||||
return "TObjCityAreaWarp";
|
||||
case 0x0045:
|
||||
return "TObjCityMapWarp";
|
||||
case 0x0046:
|
||||
return "TObjCityDoor_Shop";
|
||||
case 0x0047:
|
||||
return "TObjCityDoor_Guild";
|
||||
case 0x0048:
|
||||
return "TObjCityDoor_Warp";
|
||||
case 0x0049:
|
||||
return "TObjCityDoor_Med";
|
||||
case 0x004A:
|
||||
return "__UNNAMED_004A__";
|
||||
case 0x004B:
|
||||
return "TObjCity_Season_EasterEgg";
|
||||
case 0x004C:
|
||||
return "TObjCity_Season_ValentineHeart";
|
||||
case 0x004D:
|
||||
return "TObjCity_Season_XmasTree";
|
||||
case 0x004E:
|
||||
return "TObjCity_Season_XmasWreath";
|
||||
case 0x004F:
|
||||
return "TObjCity_Season_HalloweenPumpkin";
|
||||
case 0x0050:
|
||||
return "TObjCity_Season_21_21";
|
||||
case 0x0051:
|
||||
return "TObjCity_Season_SonicAdv2";
|
||||
case 0x0052:
|
||||
return "TObjCity_Season_Board";
|
||||
case 0x0053:
|
||||
return "TObjCity_Season_FireWorkCtrl";
|
||||
case 0x0054:
|
||||
return "TObjCityDoor_Lobby";
|
||||
case 0x0055:
|
||||
return "TObjCityMainWarpChallenge";
|
||||
case 0x0056:
|
||||
return "TODoorLabo";
|
||||
case 0x0057:
|
||||
return "TObjTradeCollision";
|
||||
case 0x0080:
|
||||
return "TObjDoor";
|
||||
case 0x0081:
|
||||
return "TObjDoorKey";
|
||||
case 0x0082:
|
||||
return "TObjLazerFenceNorm";
|
||||
case 0x0083:
|
||||
return "TObjLazerFence4";
|
||||
case 0x0084:
|
||||
return "TLazerFenceSw";
|
||||
case 0x0085:
|
||||
return "TKomorebi";
|
||||
case 0x0086:
|
||||
return "TButterfly";
|
||||
case 0x0087:
|
||||
return "TMotorcycle";
|
||||
case 0x0088:
|
||||
return "TObjContainerItem";
|
||||
case 0x0089:
|
||||
return "TObjTank";
|
||||
case 0x008B:
|
||||
return "TObjComputer";
|
||||
case 0x008C:
|
||||
return "TObjContainerIdo";
|
||||
case 0x008D:
|
||||
return "TOCapsuleAncient01";
|
||||
case 0x008E:
|
||||
return "TOBarrierEnergy01";
|
||||
case 0x008F:
|
||||
return "TObjHashi";
|
||||
case 0x0090:
|
||||
return "TOKeyGenericSw";
|
||||
case 0x0091:
|
||||
return "TObjContainerEnemy";
|
||||
case 0x0092:
|
||||
return "TObjContainerBase";
|
||||
case 0x0093:
|
||||
return "TObjContainerAbeEnemy";
|
||||
case 0x0095:
|
||||
return "TObjContainerNoItem";
|
||||
case 0x0096:
|
||||
return "TObjLazerFenceExtra";
|
||||
case 0x00C0:
|
||||
return "TOKeyCave01";
|
||||
case 0x00C1:
|
||||
return "TODoorCave01";
|
||||
case 0x00C2:
|
||||
return "TODoorCave02";
|
||||
case 0x00C3:
|
||||
return "TOHangceilingCave01Key/TOHangceilingCave01Normal/TOHangceilingCave01KeyQuick";
|
||||
case 0x00C4:
|
||||
return "TOSignCave01";
|
||||
case 0x00C5:
|
||||
return "TOSignCave02";
|
||||
case 0x00C6:
|
||||
return "TOSignCave03";
|
||||
case 0x00C7:
|
||||
return "TOAirconCave01";
|
||||
case 0x00C8:
|
||||
return "TOAirconCave02";
|
||||
case 0x00C9:
|
||||
return "TORevlightCave01";
|
||||
case 0x00CB:
|
||||
return "TORainbowCave01";
|
||||
case 0x00CC:
|
||||
return "TOKurage";
|
||||
case 0x00CD:
|
||||
return "TODragonflyCave01";
|
||||
case 0x00CE:
|
||||
return "TODoorCave03";
|
||||
case 0x00CF:
|
||||
return "TOBind";
|
||||
case 0x00D0:
|
||||
return "TOCakeshopCave01";
|
||||
case 0x00D1:
|
||||
return "TORockCaveS01";
|
||||
case 0x00D2:
|
||||
return "TORockCaveM01";
|
||||
case 0x00D3:
|
||||
return "TORockCaveL01";
|
||||
case 0x00D4:
|
||||
return "TORockCaveS02";
|
||||
case 0x00D5:
|
||||
return "TORockCaveM02";
|
||||
case 0x00D6:
|
||||
return "TORockCaveL02";
|
||||
case 0x00D7:
|
||||
return "TORockCaveSS02";
|
||||
case 0x00D8:
|
||||
return "TORockCaveSM02";
|
||||
case 0x00D9:
|
||||
return "TORockCaveSL02";
|
||||
case 0x00DA:
|
||||
return "TORockCaveS03";
|
||||
case 0x00DB:
|
||||
return "TORockCaveM03";
|
||||
case 0x00DC:
|
||||
return "TORockCaveL03";
|
||||
case 0x00DE:
|
||||
return "TODummyKeyCave01";
|
||||
case 0x00DF:
|
||||
return "TORockCaveBL01";
|
||||
case 0x00E0:
|
||||
return "TORockCaveBL02";
|
||||
case 0x00E1:
|
||||
return "TORockCaveBL03";
|
||||
case 0x0100:
|
||||
return "TODoorMachine01";
|
||||
case 0x0101:
|
||||
return "TOKeyMachine01";
|
||||
case 0x0102:
|
||||
return "TODoorMachine02";
|
||||
case 0x0103:
|
||||
return "TOCapsuleMachine01";
|
||||
case 0x0104:
|
||||
return "TOComputerMachine01";
|
||||
case 0x0105:
|
||||
return "TOMonitorMachine01";
|
||||
case 0x0106:
|
||||
return "TODragonflyMachine01";
|
||||
case 0x0107:
|
||||
return "TOLightMachine01";
|
||||
case 0x0108:
|
||||
return "TOExplosiveMachine01";
|
||||
case 0x0109:
|
||||
return "TOExplosiveMachine02";
|
||||
case 0x010A:
|
||||
return "TOExplosiveMachine03";
|
||||
case 0x010B:
|
||||
return "TOSparkMachine01";
|
||||
case 0x010C:
|
||||
return "TOHangerMachine01";
|
||||
case 0x0130:
|
||||
return "TODoorVoShip";
|
||||
case 0x0140:
|
||||
return "TObjGoalWarpAncient";
|
||||
case 0x0141:
|
||||
return "TObjMapWarpAncient";
|
||||
case 0x0142:
|
||||
return "TOKeyAncient02";
|
||||
case 0x0143:
|
||||
return "TOKeyAncient03";
|
||||
case 0x0144:
|
||||
return "TODoorAncient01";
|
||||
case 0x0145:
|
||||
return "TODoorAncient03";
|
||||
case 0x0146:
|
||||
return "TODoorAncient04";
|
||||
case 0x0147:
|
||||
return "TODoorAncient05";
|
||||
case 0x0148:
|
||||
return "TODoorAncient06";
|
||||
case 0x0149:
|
||||
return "TODoorAncient07";
|
||||
case 0x014A:
|
||||
return "TODoorAncient08";
|
||||
case 0x014B:
|
||||
return "TODoorAncient09";
|
||||
case 0x014C:
|
||||
return "TOSensorAncient01";
|
||||
case 0x014D:
|
||||
return "TOKeyAncient01";
|
||||
case 0x014E:
|
||||
return "TOFenceAncient01";
|
||||
case 0x014F:
|
||||
return "TOFenceAncient02";
|
||||
case 0x0150:
|
||||
return "TOFenceAncient03";
|
||||
case 0x0151:
|
||||
return "TOFenceAncient04";
|
||||
case 0x0152:
|
||||
return "TContainerAncient01";
|
||||
case 0x0153:
|
||||
return "TOTrapAncient01";
|
||||
case 0x0154:
|
||||
return "TOTrapAncient02";
|
||||
case 0x0155:
|
||||
return "TOMonumentAncient01";
|
||||
case 0x0156:
|
||||
return "TOMonumentAncient02";
|
||||
case 0x0159:
|
||||
return "TOWreckAncient01";
|
||||
case 0x015A:
|
||||
return "TOWreckAncient02";
|
||||
case 0x015B:
|
||||
return "TOWreckAncient03";
|
||||
case 0x015C:
|
||||
return "TOWreckAncient04";
|
||||
case 0x015D:
|
||||
return "TOWreckAncient05";
|
||||
case 0x015E:
|
||||
return "TOWreckAncient06";
|
||||
case 0x015F:
|
||||
return "TOWreckAncient07";
|
||||
case 0x0160:
|
||||
return "TObjFogCollisionPoison/TObjWarpBoss03";
|
||||
case 0x0161:
|
||||
return "TOContainerAncientItemCommon";
|
||||
case 0x0162:
|
||||
return "TOContainerAncientItemRare";
|
||||
case 0x0163:
|
||||
return "TOContainerAncientEnemyCommon";
|
||||
case 0x0164:
|
||||
return "TOContainerAncientEnemyRare";
|
||||
case 0x0165:
|
||||
return "TOContainerAncientItemNone";
|
||||
case 0x0166:
|
||||
return "TOWreckAncientBrakable05";
|
||||
case 0x0167:
|
||||
return "TOTrapAncient02R";
|
||||
case 0x0170:
|
||||
return "TOBoss4Bird";
|
||||
case 0x0171:
|
||||
return "TOBoss4Tower";
|
||||
case 0x0172:
|
||||
return "TOBoss4Rock";
|
||||
case 0x0180:
|
||||
return "TObjInfoCol";
|
||||
case 0x0181:
|
||||
return "TObjWarpLobby";
|
||||
case 0x0182:
|
||||
return "TObjLobbyMain";
|
||||
case 0x0183:
|
||||
return "__TObjPathObj_subclass_0183__";
|
||||
case 0x0184:
|
||||
return "TObjButterflyLobby";
|
||||
case 0x0185:
|
||||
return "TObjRainbowLobby";
|
||||
case 0x0186:
|
||||
return "TObjKabochaLobby";
|
||||
case 0x0187:
|
||||
return "TObjStendGlassLobby";
|
||||
case 0x0188:
|
||||
return "TObjCurtainLobby";
|
||||
case 0x0189:
|
||||
return "TObjWeddingLobby";
|
||||
case 0x018A:
|
||||
return "TObjTreeLobby";
|
||||
case 0x018B:
|
||||
return "TObjSuisouLobby";
|
||||
case 0x018C:
|
||||
return "TObjParticleLobby";
|
||||
case 0x0190:
|
||||
return "TObjCamera";
|
||||
case 0x0191:
|
||||
return "TObjTuitate";
|
||||
case 0x0192:
|
||||
return "TObjDoaEx01";
|
||||
case 0x0193:
|
||||
return "TObjBigTuitate";
|
||||
case 0x01A0:
|
||||
return "TODoorVS2Door01";
|
||||
case 0x01A1:
|
||||
return "TOVS2Wreck01";
|
||||
case 0x01A2:
|
||||
return "TOVS2Wreck02";
|
||||
case 0x01A3:
|
||||
return "TOVS2Wreck03";
|
||||
case 0x01A4:
|
||||
return "TOVS2Wreck04";
|
||||
case 0x01A5:
|
||||
return "TOVS2Wreck05";
|
||||
case 0x01A6:
|
||||
return "TOVS2Wreck06";
|
||||
case 0x01A7:
|
||||
return "TOVS2Wall01";
|
||||
case 0x01A8:
|
||||
return "__UNNAMED_01A8__";
|
||||
case 0x01A9:
|
||||
return "TObjHashiVersus1";
|
||||
case 0x01AA:
|
||||
return "TObjHashiVersus2";
|
||||
case 0x01AB:
|
||||
return "TODoorFourLightRuins";
|
||||
case 0x01C0:
|
||||
return "TODoorFourLightSpace";
|
||||
case 0x0200:
|
||||
return "TObjContainerJung";
|
||||
case 0x0201:
|
||||
return "TObjWarpJung";
|
||||
case 0x0202:
|
||||
return "TObjDoorJung";
|
||||
case 0x0203:
|
||||
return "TObjContainerJungEx";
|
||||
case 0x0204:
|
||||
return "TODoorJungleMain";
|
||||
case 0x0205:
|
||||
return "TOKeyJungleMain";
|
||||
case 0x0206:
|
||||
return "TORockJungleS01";
|
||||
case 0x0207:
|
||||
return "TORockJungleM01";
|
||||
case 0x0208:
|
||||
return "TORockJungleL01";
|
||||
case 0x0209:
|
||||
return "TOGrassJungle";
|
||||
case 0x020A:
|
||||
return "TObjWarpJungMain";
|
||||
case 0x020B:
|
||||
return "TBGLightningCtrl";
|
||||
case 0x020C:
|
||||
return "__TObjPathObj_subclass_020C__";
|
||||
case 0x020D:
|
||||
return "__TObjPathObj_subclass_020D__";
|
||||
case 0x020E:
|
||||
return "TObjContainerJungEnemy";
|
||||
case 0x020F:
|
||||
return "TOTrapChainSawDamage";
|
||||
case 0x0210:
|
||||
return "TOTrapChainSawKey";
|
||||
case 0x0211:
|
||||
return "TOBiwaMushi";
|
||||
case 0x0212:
|
||||
return "__TObjPathObj_subclass_0212__";
|
||||
case 0x0213:
|
||||
return "TOJungleDesign";
|
||||
case 0x0220:
|
||||
return "TObjFish";
|
||||
case 0x0221:
|
||||
return "TODoorFourLightSeabed";
|
||||
case 0x0222:
|
||||
return "TODoorFourLightSeabedU";
|
||||
case 0x0223:
|
||||
return "TObjSeabedSuiso_CH";
|
||||
case 0x0224:
|
||||
return "TObjSeabedSuisoBrakable";
|
||||
case 0x0225:
|
||||
return "TOMekaFish00";
|
||||
case 0x0226:
|
||||
return "TOMekaFish01";
|
||||
case 0x0227:
|
||||
return "__TObjPathObj_subclass_0227__";
|
||||
case 0x0228:
|
||||
return "TOTrapSeabed01";
|
||||
case 0x0229:
|
||||
return "TOCapsuleLabo";
|
||||
case 0x0240:
|
||||
return "TObjParticle";
|
||||
case 0x0280:
|
||||
return "__TObjAreaWarpForest_subclass_0280__";
|
||||
case 0x02A0:
|
||||
return "TObjLiveCamera";
|
||||
case 0x02B0:
|
||||
return "TContainerAncient01R";
|
||||
case 0x02B1:
|
||||
return "TObjLaboDesignBase";
|
||||
case 0x02B2:
|
||||
return "TObjLaboDesignBase";
|
||||
case 0x02B3:
|
||||
return "TObjLaboDesignBase";
|
||||
case 0x02B4:
|
||||
return "TObjLaboDesignBase";
|
||||
case 0x02B5:
|
||||
return "TObjLaboDesignBase";
|
||||
case 0x02B6:
|
||||
return "TObjLaboDesignBase";
|
||||
case 0x02B7:
|
||||
return "TObjGbAdvance";
|
||||
case 0x02B8:
|
||||
return "TObjQuestColALock2";
|
||||
case 0x02B9:
|
||||
return "TObjMapForceWarp";
|
||||
case 0x02BA:
|
||||
return "TObjQuestCol2";
|
||||
case 0x02BB:
|
||||
return "TODoorLaboNormal";
|
||||
case 0x02BC:
|
||||
return "TObjAreaWarpEndingJung";
|
||||
case 0x02BD:
|
||||
return "TObjLaboMapWarp";
|
||||
case 0x0300:
|
||||
return "__UNKNOWN_0300__";
|
||||
case 0x0301:
|
||||
return "__UNKNOWN_0301__";
|
||||
case 0x0302:
|
||||
return "__UNKNOWN_0302__";
|
||||
case 0x0303:
|
||||
return "__UNKNOWN_0303__";
|
||||
case 0x0340:
|
||||
return "__UNKNOWN_0340__";
|
||||
case 0x0341:
|
||||
return "__UNKNOWN_0341__";
|
||||
case 0x0380:
|
||||
return "__UNKNOWN_0380__";
|
||||
case 0x0381:
|
||||
return "__UNKNOWN_0381__";
|
||||
case 0x0382:
|
||||
return "__UNKNOWN_0382__";
|
||||
case 0x0383:
|
||||
return "__UNKNOWN_0383__";
|
||||
case 0x0385:
|
||||
return "__UNKNOWN_0385__";
|
||||
case 0x0386:
|
||||
return "__UNKNOWN_0386__";
|
||||
case 0x0387:
|
||||
return "__UNKNOWN_0387__";
|
||||
case 0x0388:
|
||||
return "__UNKNOWN_0388__";
|
||||
case 0x0389:
|
||||
return "__UNKNOWN_0389__";
|
||||
case 0x038A:
|
||||
return "__UNKNOWN_038A__";
|
||||
case 0x038B:
|
||||
return "__UNKNOWN_038B__";
|
||||
case 0x038C:
|
||||
return "__UNKNOWN_038C__";
|
||||
case 0x038D:
|
||||
return "__UNKNOWN_038D__";
|
||||
case 0x038E:
|
||||
return "__UNKNOWN_038E__";
|
||||
case 0x038F:
|
||||
return "__UNKNOWN_038F__";
|
||||
case 0x0390:
|
||||
return "__UNKNOWN_0390__";
|
||||
case 0x0391:
|
||||
return "__UNKNOWN_0391__";
|
||||
case 0x03C0:
|
||||
return "__UNKNOWN_03C0__";
|
||||
case 0x03C1:
|
||||
return "__UNKNOWN_03C1__";
|
||||
default:
|
||||
return "__UNKNOWN__";
|
||||
}
|
||||
}
|
||||
|
||||
Map::RareEnemyRates::RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate)
|
||||
: hildeblue(enemy_rate),
|
||||
rappy(enemy_rate),
|
||||
@@ -100,8 +655,9 @@ string Map::EnemyEntry::str() const {
|
||||
this->unused.load());
|
||||
}
|
||||
|
||||
Map::Enemy::Enemy(size_t source_index, uint8_t floor, EnemyType type)
|
||||
Map::Enemy::Enemy(uint16_t enemy_id, size_t source_index, uint8_t floor, EnemyType type)
|
||||
: source_index(source_index),
|
||||
enemy_id(enemy_id),
|
||||
type(type),
|
||||
floor(floor),
|
||||
state_flags(0),
|
||||
@@ -109,19 +665,22 @@ Map::Enemy::Enemy(size_t source_index, uint8_t floor, EnemyType type)
|
||||
}
|
||||
|
||||
string Map::Enemy::str() const {
|
||||
return string_printf("[Map::Enemy source %zX %s floor=%02hhX flags=%02hhX last_hit_by_client_id=%hu]",
|
||||
this->source_index, name_for_enum(this->type), this->floor, this->state_flags, this->last_hit_by_client_id);
|
||||
return string_printf("[Map::Enemy E-%hX source %zX %s floor=%02hhX flags=%02hhX last_hit_by_client_id=%hu]",
|
||||
this->enemy_id, this->source_index, name_for_enum(this->type), this->floor, this->state_flags, this->last_hit_by_client_id);
|
||||
}
|
||||
|
||||
string Map::Object::str() const {
|
||||
if (this->param1 <= 0.0f) {
|
||||
return string_printf("[Map::Object source %zX %04hX @%04hX p1=%g (specialized: %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ") floor=%02hhX item_drop_checked=%s]",
|
||||
this->source_index, this->base_type, this->section, this->param1, this->param4, this->param5, this->param6, this->floor, this->item_drop_checked ? "true" : "false");
|
||||
} else {
|
||||
return string_printf("[Map::Object source %zX %04hX @%04hX p1=%g (generic) p456=[%08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] floor=%02hhX item_drop_checked=%s]",
|
||||
this->source_index, this->base_type, this->section, this->param1, this->param4, this->param5, this->param6,
|
||||
this->floor, this->item_drop_checked ? "true" : "false");
|
||||
}
|
||||
return string_printf("[Map::Object source %zX %04hX(%s) @%04hX p1=%g p456=[%08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] floor=%02hhX item_drop_checked=%s]",
|
||||
this->source_index,
|
||||
this->base_type,
|
||||
Map::name_for_object_type(this->base_type),
|
||||
this->section,
|
||||
this->param1,
|
||||
this->param4,
|
||||
this->param5,
|
||||
this->param6,
|
||||
this->floor,
|
||||
this->item_drop_checked ? "true" : "false");
|
||||
}
|
||||
|
||||
Map::Map(uint32_t lobby_id, std::shared_ptr<PSOLFGEncryption> random_crypt)
|
||||
@@ -142,8 +701,11 @@ void Map::add_objects_from_map_data(uint8_t floor, const void* data, size_t size
|
||||
|
||||
const auto* objects = reinterpret_cast<const ObjectEntry*>(data);
|
||||
for (size_t z = 0; z < entry_count; z++) {
|
||||
uint16_t object_id = this->objects.size();
|
||||
this->objects.emplace_back(Object{
|
||||
.source_index = z,
|
||||
.object_id = object_id,
|
||||
.floor = floor,
|
||||
.base_type = objects[z].base_type,
|
||||
.section = objects[z].section,
|
||||
.param1 = objects[z].param1,
|
||||
@@ -151,7 +713,6 @@ void Map::add_objects_from_map_data(uint8_t floor, const void* data, size_t size
|
||||
.param4 = objects[z].param4,
|
||||
.param5 = objects[z].param5,
|
||||
.param6 = objects[z].param6,
|
||||
.floor = floor,
|
||||
.item_drop_checked = false,
|
||||
});
|
||||
}
|
||||
@@ -177,7 +738,8 @@ void Map::add_enemy(
|
||||
const EnemyEntry& e,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates) {
|
||||
auto add = [&](EnemyType type) -> void {
|
||||
this->enemies.emplace_back(index, floor, type);
|
||||
uint16_t enemy_id = this->enemies.size();
|
||||
this->enemies.emplace_back(enemy_id, index, floor, type);
|
||||
};
|
||||
|
||||
EnemyType child_type = EnemyType::UNKNOWN;
|
||||
@@ -956,6 +1518,24 @@ void Map::add_enemies_and_objects_from_quest_data(
|
||||
}
|
||||
}
|
||||
|
||||
const Map::Enemy& Map::find_enemy(uint8_t floor, EnemyType type) const {
|
||||
return const_cast<Map*>(this)->find_enemy(floor, type);
|
||||
}
|
||||
|
||||
Map::Enemy& Map::find_enemy(uint8_t floor, EnemyType type) {
|
||||
if (enemies.empty()) {
|
||||
throw out_of_range("no enemies defined");
|
||||
}
|
||||
// TODO: Linear search is bad here. Do something better, like binary search
|
||||
// for the floor start and just linear search through the floor enemies.
|
||||
for (auto& e : this->enemies) {
|
||||
if (e.floor == floor && e.type == type) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
throw out_of_range("enemy not found");
|
||||
}
|
||||
|
||||
string Map::disassemble_quest_data(const void* data, size_t size) {
|
||||
auto all_floor_sections = Map::collect_quest_map_data_sections(data, size);
|
||||
|
||||
@@ -1089,12 +1669,12 @@ void SetDataTable::print(FILE* stream) const {
|
||||
}
|
||||
}
|
||||
|
||||
struct AreaMapFileIndex {
|
||||
struct AreaMapFileInfo {
|
||||
const char* name_token;
|
||||
vector<uint32_t> variation1_values;
|
||||
vector<uint32_t> variation2_values;
|
||||
|
||||
AreaMapFileIndex(
|
||||
AreaMapFileInfo(
|
||||
const char* name_token,
|
||||
vector<uint32_t> variation1_values,
|
||||
vector<uint32_t> variation2_values)
|
||||
@@ -1103,8 +1683,27 @@ struct AreaMapFileIndex {
|
||||
variation2_values(variation2_values) {}
|
||||
};
|
||||
|
||||
static const vector<AreaMapFileInfo> map_file_info_dc_protos = {
|
||||
{"city00", {}, {0}},
|
||||
{"forest01", {}, {0, 1}},
|
||||
{"forest02", {}, {0, 1}},
|
||||
{"cave01", {0, 1}, {0, 1}},
|
||||
{"cave02", {0, 1}, {0, 1}},
|
||||
{"cave03", {0, 1}, {0, 1}},
|
||||
{"machine01", {0}, {0, 1}},
|
||||
{"machine02", {0}, {0, 1}},
|
||||
{"ancient01", {0, 1}, {0, 1}},
|
||||
{"ancient02", {0, 1}, {0, 1}},
|
||||
{"ancient03", {0, 1}, {0, 1}},
|
||||
{"boss01", {}, {}},
|
||||
{"boss02", {}, {}},
|
||||
{"boss03", {}, {}},
|
||||
{"boss04", {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
};
|
||||
|
||||
// These are indexed as [episode][is_solo][floor], where episode is 0-2
|
||||
static const vector<vector<vector<AreaMapFileIndex>>> map_file_info = {
|
||||
static const vector<vector<vector<AreaMapFileInfo>>> map_file_info = {
|
||||
{
|
||||
// Episode 1
|
||||
{
|
||||
@@ -1230,96 +1829,116 @@ static const vector<vector<vector<AreaMapFileIndex>>> map_file_info = {
|
||||
},
|
||||
};
|
||||
|
||||
const vector<vector<AreaMapFileIndex>>& map_file_info_for_episode(Episode ep) {
|
||||
switch (ep) {
|
||||
case Episode::EP1:
|
||||
return map_file_info.at(0);
|
||||
case Episode::EP2:
|
||||
return map_file_info.at(1);
|
||||
case Episode::EP4:
|
||||
return map_file_info.at(2);
|
||||
default:
|
||||
throw invalid_argument("episode has no maps");
|
||||
const AreaMapFileInfo& file_info_for_variation(
|
||||
Version version, Episode episode, uint8_t area, bool is_solo) {
|
||||
const vector<AreaMapFileInfo>* multi_index = nullptr;
|
||||
const vector<AreaMapFileInfo>* solo_index = nullptr;
|
||||
if (is_pre_v1(version)) {
|
||||
multi_index = &map_file_info_dc_protos;
|
||||
} else {
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
multi_index = &map_file_info.at(0).at(0);
|
||||
solo_index = &map_file_info.at(0).at(1);
|
||||
break;
|
||||
case Episode::EP2:
|
||||
multi_index = &map_file_info.at(1).at(0);
|
||||
solo_index = &map_file_info.at(1).at(1);
|
||||
break;
|
||||
case Episode::EP3: {
|
||||
static const AreaMapFileInfo blank_info = {nullptr, {}, {}};
|
||||
return blank_info;
|
||||
}
|
||||
case Episode::EP4:
|
||||
multi_index = &map_file_info.at(2).at(0);
|
||||
solo_index = &map_file_info.at(2).at(1);
|
||||
break;
|
||||
default:
|
||||
throw invalid_argument("episode has no maps");
|
||||
}
|
||||
}
|
||||
|
||||
if (is_solo && solo_index) {
|
||||
const auto& ret = solo_index->at(area);
|
||||
if (ret.name_token) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return multi_index->at(area);
|
||||
}
|
||||
|
||||
void generate_variations(
|
||||
parray<le_uint32_t, 0x20>& variations,
|
||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
Version version,
|
||||
Episode episode,
|
||||
bool is_solo) {
|
||||
const auto& ep_index = map_file_info_for_episode(episode);
|
||||
for (size_t z = 0; z < 0x10; z++) {
|
||||
const AreaMapFileIndex* a = nullptr;
|
||||
if (is_solo) {
|
||||
a = &ep_index.at(true).at(z);
|
||||
}
|
||||
if (!a || !a->name_token) {
|
||||
a = &ep_index.at(false).at(z);
|
||||
}
|
||||
if (!a->name_token) {
|
||||
const auto& a = file_info_for_variation(version, episode, z, is_solo);
|
||||
if (!a.name_token) {
|
||||
variations[z * 2 + 0] = 0;
|
||||
variations[z * 2 + 1] = 0;
|
||||
} else {
|
||||
variations[z * 2 + 0] = (a->variation1_values.size() < 2) ? 0 : (random_crypt->next() % a->variation1_values.size());
|
||||
variations[z * 2 + 1] = (a->variation2_values.size() < 2) ? 0 : (random_crypt->next() % a->variation2_values.size());
|
||||
variations[z * 2 + 0] = (a.variation1_values.size() < 2) ? 0 : (random_crypt->next() % a.variation1_values.size());
|
||||
variations[z * 2 + 1] = (a.variation2_values.size() < 2) ? 0 : (random_crypt->next() % a.variation2_values.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generate_variations_dc_nte(
|
||||
parray<le_uint32_t, 0x20>& variations,
|
||||
shared_ptr<PSOLFGEncryption> random_crypt) {
|
||||
static const std::array<uint32_t, 0x20> maxes(
|
||||
{1, 1, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1});
|
||||
for (size_t z = 0; z < 0x20; z++) {
|
||||
variations[z] = (maxes[z] < 2) ? 0 : (random_crypt->next() % maxes[z]);
|
||||
}
|
||||
}
|
||||
|
||||
vector<string> map_filenames_for_variation(
|
||||
Episode episode, bool is_solo, uint8_t floor, uint32_t var1, uint32_t var2, bool is_enemies) {
|
||||
// Map filenames are like map_<name_token>[_VV][_VV][_off]<e|o>[_s].dat
|
||||
// name_token comes from AreaMapFileIndex
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t floor,
|
||||
uint32_t var1,
|
||||
uint32_t var2,
|
||||
bool is_enemies) {
|
||||
// Map filenames are like map_<name_token>_<VV>_<VV>(_off)?(e|o)(_s|_c1)?.dat
|
||||
// name_token comes from AreaMapFileInfo
|
||||
// _VV are the values from the variation<1|2>_values vector (in contrast to
|
||||
// the values sent in the 64 command, which are INDEXES INTO THAT VECTOR)
|
||||
// _off or _s are used for solo mode (try both - city uses _s whereas levels
|
||||
// use _off apparently)
|
||||
// _c1 is used for the city map in Challenge mode (which we don't load,
|
||||
// since it contains only NPCs and not enemies)
|
||||
// e|o specifies what kind of data: e = enemies, o = objects
|
||||
const auto& ep_index = map_file_info_for_episode(episode);
|
||||
const AreaMapFileIndex* a = nullptr;
|
||||
if (is_solo) {
|
||||
a = &ep_index.at(true).at(floor);
|
||||
}
|
||||
if (!a || !a->name_token) {
|
||||
a = &ep_index.at(false).at(floor);
|
||||
}
|
||||
if (!a->name_token) {
|
||||
const auto& a = file_info_for_variation(version, episode, floor, mode == GameMode::SOLO);
|
||||
if (!a.name_token) {
|
||||
return vector<string>();
|
||||
}
|
||||
|
||||
string filename = "map_";
|
||||
filename += a->name_token;
|
||||
if (!a->variation1_values.empty()) {
|
||||
filename += string_printf("_%02" PRIX32, a->variation1_values.at(var1));
|
||||
filename += a.name_token;
|
||||
if (!a.variation1_values.empty()) {
|
||||
filename += string_printf("_%02" PRIX32, a.variation1_values.at(var1));
|
||||
}
|
||||
if (!a->variation2_values.empty()) {
|
||||
filename += string_printf("_%02" PRIX32, a->variation2_values.at(var2));
|
||||
if (!a.variation2_values.empty()) {
|
||||
filename += string_printf("_%02" PRIX32, a.variation2_values.at(var2));
|
||||
}
|
||||
|
||||
// Try both _off<e|o>.dat and <e|o>_s.dat suffixes first before falling back
|
||||
// to non-solo version
|
||||
vector<string> ret;
|
||||
if (is_enemies) {
|
||||
if (is_solo) {
|
||||
if (mode == GameMode::SOLO) {
|
||||
ret.emplace_back(filename + "_offe.dat");
|
||||
ret.emplace_back(filename + "e_s.dat");
|
||||
} else if (floor == 0) {
|
||||
if (mode == GameMode::BATTLE) {
|
||||
ret.emplace_back(filename + "e_d.dat");
|
||||
} else if (mode == GameMode::CHALLENGE) {
|
||||
ret.emplace_back(filename + "e_c1.dat");
|
||||
}
|
||||
}
|
||||
ret.emplace_back(filename + "e.dat");
|
||||
} else {
|
||||
if (is_solo) {
|
||||
if (mode == GameMode::SOLO) {
|
||||
ret.emplace_back(filename + "_offo.dat");
|
||||
ret.emplace_back(filename + "o_s.dat");
|
||||
} else if (floor == 0) {
|
||||
if (mode == GameMode::BATTLE) {
|
||||
ret.emplace_back(filename + "o_d.dat");
|
||||
} else if (mode == GameMode::CHALLENGE) {
|
||||
ret.emplace_back(filename + "o_c1.dat");
|
||||
}
|
||||
}
|
||||
ret.emplace_back(filename + "o.dat");
|
||||
}
|
||||
|
||||
+11
-6
@@ -15,6 +15,8 @@
|
||||
#include "Text.hh"
|
||||
|
||||
struct Map {
|
||||
static const char* name_for_object_type(uint16_t type);
|
||||
|
||||
struct SectionHeader {
|
||||
enum class Type {
|
||||
END = 0,
|
||||
@@ -206,6 +208,8 @@ 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 base_type;
|
||||
uint16_t section;
|
||||
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
|
||||
@@ -213,7 +217,6 @@ struct Map {
|
||||
uint32_t param4;
|
||||
uint32_t param5;
|
||||
uint32_t param6;
|
||||
uint8_t floor;
|
||||
bool item_drop_checked;
|
||||
|
||||
std::string str() const;
|
||||
@@ -228,12 +231,13 @@ struct Map {
|
||||
ITEM_DROPPED = 0x10,
|
||||
};
|
||||
size_t source_index;
|
||||
uint16_t enemy_id;
|
||||
EnemyType type;
|
||||
uint8_t floor;
|
||||
uint8_t state_flags;
|
||||
uint8_t last_hit_by_client_id;
|
||||
|
||||
Enemy(size_t source_index, uint8_t floor, EnemyType type);
|
||||
Enemy(uint16_t enemy_id, size_t source_index, uint8_t floor, EnemyType type);
|
||||
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
@@ -305,6 +309,9 @@ struct Map {
|
||||
uint32_t rare_seed,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = Map::DEFAULT_RARE_ENEMIES);
|
||||
|
||||
const Enemy& find_enemy(uint8_t floor, EnemyType type) const;
|
||||
Enemy& find_enemy(uint8_t floor, EnemyType type);
|
||||
|
||||
static std::string disassemble_quest_data(const void* data, size_t size);
|
||||
|
||||
PrefixedLogger log;
|
||||
@@ -346,10 +353,8 @@ private:
|
||||
void generate_variations(
|
||||
parray<le_uint32_t, 0x20>& variations,
|
||||
std::shared_ptr<PSOLFGEncryption> random,
|
||||
Version version,
|
||||
Episode episode,
|
||||
bool is_solo);
|
||||
void generate_variations_dc_nte(
|
||||
parray<le_uint32_t, 0x20>& variations,
|
||||
std::shared_ptr<PSOLFGEncryption> random);
|
||||
std::vector<std::string> map_filenames_for_variation(
|
||||
Episode episode, bool is_solo, uint8_t floor, uint32_t var1, uint32_t var2, bool is_enemies);
|
||||
Version version, Episode episode, GameMode mode, uint8_t floor, uint32_t var1, uint32_t var2, bool is_enemies);
|
||||
|
||||
+13
-18
@@ -2001,11 +2001,16 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
|
||||
}
|
||||
|
||||
auto s = l->require_server_state();
|
||||
// For Challenge quests, don't replace the map now - the leader will send an
|
||||
// 02DF command to create overlays, which also replaces the map.
|
||||
if ((l->base_version == Version::BB_V4) && l->map && (l->quest->challenge_template_index < 0)) {
|
||||
|
||||
// For BB Challenge quests, don't replace the map now - the leader will send
|
||||
// an 02DF command to create overlays, which also replaces the map. (We do
|
||||
// this because 02DF is also sent when a challenge is failed and retried,
|
||||
// which reloads the map and recreates character overlays anyway.)
|
||||
if ((l->base_version != Version::BB_V4) || (l->quest->challenge_template_index < 0)) {
|
||||
l->load_maps();
|
||||
}
|
||||
|
||||
// Delete all floor items
|
||||
for (auto& m : l->floor_item_managers) {
|
||||
m.clear();
|
||||
}
|
||||
@@ -2443,9 +2448,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
if (game->count_clients() == 1) {
|
||||
// No one was in the game before, so the object and enemy state is lost;
|
||||
// regenerate it as if the game was just created
|
||||
if ((game->base_version == Version::BB_V4) && game->map) {
|
||||
game->load_maps();
|
||||
}
|
||||
game->load_maps();
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE);
|
||||
}
|
||||
break;
|
||||
@@ -4175,23 +4178,15 @@ shared_ptr<Lobby> create_game_generic(
|
||||
|
||||
bool is_solo = (game->mode == GameMode::SOLO);
|
||||
|
||||
// Generate the map variations
|
||||
if (game->is_ep3()) {
|
||||
game->variations.clear(0);
|
||||
} else if ((c->version() == Version::DC_NTE) || (c->version() == Version::DC_V1_11_2000_PROTOTYPE)) {
|
||||
generate_variations_dc_nte(game->variations, game->random_crypt);
|
||||
} else {
|
||||
generate_variations(game->variations, game->random_crypt, game->episode, is_solo);
|
||||
}
|
||||
|
||||
if (game->mode == GameMode::CHALLENGE) {
|
||||
game->rare_enemy_rates = s->rare_enemy_rates_challenge;
|
||||
} else {
|
||||
game->rare_enemy_rates = s->rare_enemy_rates_by_difficulty.at(game->difficulty);
|
||||
}
|
||||
if (game->base_version == Version::BB_V4) {
|
||||
game->load_maps();
|
||||
}
|
||||
|
||||
generate_variations(game->variations, game->random_crypt, game->base_version, game->episode, is_solo);
|
||||
game->load_maps();
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
|
||||
+164
-120
@@ -2097,7 +2097,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
cmd.rt_index = in_cmd.rt_index;
|
||||
cmd.x = in_cmd.x;
|
||||
cmd.z = in_cmd.z;
|
||||
cmd.ignore_def = 1;
|
||||
cmd.ignore_def = in_cmd.ignore_def;
|
||||
cmd.effective_area = in_cmd.effective_area;
|
||||
} else {
|
||||
const auto& in_cmd = check_size_t<G_StandardDropItemRequest_DC_6x60>(data, size);
|
||||
@@ -2109,83 +2109,154 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
cmd.rt_index = in_cmd.rt_index;
|
||||
cmd.x = in_cmd.x;
|
||||
cmd.z = in_cmd.z;
|
||||
cmd.ignore_def = 1;
|
||||
cmd.ignore_def = in_cmd.ignore_def;
|
||||
cmd.effective_area = in_cmd.floor;
|
||||
}
|
||||
|
||||
auto generate_item = [&]() -> ItemCreator::DropResult {
|
||||
if (cmd.rt_index == 0x30) {
|
||||
if (l->map) {
|
||||
auto& object = l->map->objects.at(cmd.entity_id);
|
||||
Map::Object* map_object = nullptr;
|
||||
Map::Enemy* map_enemy = nullptr;
|
||||
bool ignore_def = (cmd.ignore_def != 0);
|
||||
uint8_t effective_rt_index = 0xFF;
|
||||
if (cmd.rt_index == 0x30) {
|
||||
if (l->map) {
|
||||
map_object = &l->map->objects.at(cmd.entity_id);
|
||||
l->log.info("Drop check for K-%hX %c %s",
|
||||
map_object->object_id, ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
|
||||
if (cmd.floor != map_object->floor) {
|
||||
l->log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX", cmd.floor, map_object->floor);
|
||||
}
|
||||
if (is_v1_or_v2(l->base_version) && (l->base_version != Version::GC_NTE)) {
|
||||
// V1 and V2 don't have 6xA2, so we can't get ignore_def or the object
|
||||
// parameters from the client on those versions
|
||||
cmd.param3 = map_object->param3;
|
||||
cmd.param4 = map_object->param4;
|
||||
cmd.param5 = map_object->param5;
|
||||
cmd.param6 = map_object->param6;
|
||||
}
|
||||
bool object_ignore_def = (map_object->param1 > 0.0);
|
||||
if (ignore_def != object_ignore_def) {
|
||||
l->log.warning("ignore_def value %s from command does not match object\'s expected ignore_def %s (from p1=%g)",
|
||||
ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", map_object->param1);
|
||||
}
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_printf(c, "$C5K-%hX %c %s",
|
||||
map_object->object_id, ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd.floor != object.floor) {
|
||||
l->log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX", cmd.floor, object.floor);
|
||||
}
|
||||
bool object_ignore_def = (object.param1 > 0.0);
|
||||
if (cmd.ignore_def != object_ignore_def) {
|
||||
l->log.warning("ignore_def value %s from command does not match object\'s expected ignore_def %s (from p1=%g)",
|
||||
cmd.ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", object.param1);
|
||||
} else {
|
||||
if (l->map) {
|
||||
map_enemy = &l->map->enemies.at(cmd.entity_id);
|
||||
l->log.info("Drop check for E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
|
||||
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
|
||||
// ignore what the client sends.
|
||||
if ((l->episode != Episode::EP4) && (cmd.rt_index != effective_rt_index)) {
|
||||
l->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, effective_rt_index);
|
||||
if (!is_v4(l->base_version)) {
|
||||
effective_rt_index = cmd.rt_index;
|
||||
}
|
||||
}
|
||||
if (cmd.floor != map_enemy->floor) {
|
||||
l->log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX",
|
||||
cmd.floor, map_enemy->floor);
|
||||
}
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_printf(c, "$C5E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd.ignore_def) {
|
||||
bool should_drop = true;
|
||||
if (map_object) {
|
||||
if (map_object->item_drop_checked) {
|
||||
l->log.info("Drop check has already occurred for K-%04hX; skipping it", map_object->object_id);
|
||||
should_drop = false;
|
||||
} else {
|
||||
map_object->item_drop_checked = true;
|
||||
}
|
||||
}
|
||||
if (map_enemy) {
|
||||
if (map_enemy->state_flags & Map::Enemy::Flag::ITEM_DROPPED) {
|
||||
l->log.info("Drop check has already occurred for E-%04hX; skipping it", map_enemy->enemy_id);
|
||||
should_drop = false;
|
||||
} else {
|
||||
map_enemy->state_flags |= Map::Enemy::Flag::ITEM_DROPPED;
|
||||
}
|
||||
}
|
||||
|
||||
if (should_drop) {
|
||||
auto generate_item = [&]() -> ItemCreator::DropResult {
|
||||
if (cmd.rt_index == 0x30) {
|
||||
if (ignore_def) {
|
||||
l->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
return l->item_creator->on_box_item_drop(cmd.entity_id, cmd.effective_area);
|
||||
return l->item_creator->on_box_item_drop(cmd.effective_area);
|
||||
} else {
|
||||
l->log.info("Creating item from box %04hX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")",
|
||||
cmd.entity_id.load(), cmd.effective_area, object.param3, object.param4, object.param5, object.param6);
|
||||
return l->item_creator->on_specialized_box_item_drop(cmd.entity_id, cmd.effective_area, object.param3, object.param4, object.param5, object.param6);
|
||||
cmd.entity_id.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
|
||||
return l->item_creator->on_specialized_box_item_drop(cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6);
|
||||
}
|
||||
|
||||
} else if (cmd.ignore_def) {
|
||||
l->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
return l->item_creator->on_box_item_drop(cmd.entity_id, cmd.effective_area);
|
||||
|
||||
} else {
|
||||
l->log.info("Creating item from box %04hX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")",
|
||||
cmd.entity_id.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
|
||||
return l->item_creator->on_specialized_box_item_drop(
|
||||
cmd.entity_id, cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6);
|
||||
l->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
return l->item_creator->on_monster_item_drop(effective_rt_index, cmd.effective_area);
|
||||
}
|
||||
};
|
||||
|
||||
} else {
|
||||
l->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
switch (l->drop_mode) {
|
||||
case Lobby::DropMode::DISABLED:
|
||||
case Lobby::DropMode::CLIENT:
|
||||
throw logic_error("unhandled simple drop mode");
|
||||
case Lobby::DropMode::SERVER_SHARED:
|
||||
case Lobby::DropMode::SERVER_DUPLICATE: {
|
||||
// TODO: In SERVER_DUPLICATE mode, should we reduce the rates for rare
|
||||
// items? Maybe by a factor of l->count_clients()?
|
||||
auto res = generate_item();
|
||||
if (res.item.empty()) {
|
||||
l->log.info("No item was created");
|
||||
} else {
|
||||
string name = s->describe_item(l->base_version, res.item, false);
|
||||
l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
if (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE) {
|
||||
for (const auto& lc : l->clients) {
|
||||
if (lc && ((cmd.rt_index == 0x30) || (lc->floor == cmd.floor))) {
|
||||
res.item.id = l->generate_item_id(0xFF);
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
|
||||
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id));
|
||||
send_drop_item_to_channel(s, lc->channel, res.item, cmd.rt_index != 0x30, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
if (res.is_from_rare_table) {
|
||||
send_rare_notification_if_needed(lc, res.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t effective_rt_index = cmd.rt_index;
|
||||
if (l->map) {
|
||||
const auto& enemy = l->map->enemies.at(cmd.entity_id);
|
||||
effective_rt_index = rare_table_index_for_enemy_type(enemy.type);
|
||||
// rt_indexes in Episode 4 don't match those sent in the command; we just
|
||||
// ignore what the client sends.
|
||||
if ((l->episode != Episode::EP4) && (cmd.rt_index != effective_rt_index)) {
|
||||
l->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, effective_rt_index);
|
||||
}
|
||||
if (cmd.floor != enemy.floor) {
|
||||
l->log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX",
|
||||
cmd.floor, enemy.floor);
|
||||
} else {
|
||||
res.item.id = l->generate_item_id(0xFF);
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load());
|
||||
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x00F);
|
||||
send_drop_item_to_lobby(l, res.item, cmd.rt_index != 0x30, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
if (res.is_from_rare_table) {
|
||||
for (auto lc : l->clients) {
|
||||
if (lc) {
|
||||
send_rare_notification_if_needed(lc, res.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return l->item_creator->on_monster_item_drop(cmd.entity_id, effective_rt_index, cmd.effective_area);
|
||||
}
|
||||
};
|
||||
|
||||
switch (l->drop_mode) {
|
||||
case Lobby::DropMode::DISABLED:
|
||||
case Lobby::DropMode::CLIENT:
|
||||
throw logic_error("unhandled simple drop mode");
|
||||
case Lobby::DropMode::SERVER_SHARED:
|
||||
case Lobby::DropMode::SERVER_DUPLICATE: {
|
||||
// TODO: In SERVER_DUPLICATE mode, should we reduce the rates for rare
|
||||
// items? Maybe by a factor of l->count_clients()?
|
||||
auto res = generate_item();
|
||||
if (res.item.empty()) {
|
||||
l->log.info("No item was created");
|
||||
} else {
|
||||
string name = s->describe_item(l->base_version, res.item, false);
|
||||
l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
if (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE) {
|
||||
for (const auto& lc : l->clients) {
|
||||
if (lc && ((cmd.rt_index == 0x30) || (lc->floor == cmd.floor))) {
|
||||
case Lobby::DropMode::SERVER_PRIVATE: {
|
||||
for (const auto& lc : l->clients) {
|
||||
if (lc && ((cmd.rt_index == 0x30) || (lc->floor == cmd.floor))) {
|
||||
auto res = generate_item();
|
||||
if (res.item.empty()) {
|
||||
l->log.info("No item was created for %s", lc->channel.name.c_str());
|
||||
} else {
|
||||
string name = s->describe_item(l->base_version, res.item, false);
|
||||
l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
res.item.id = l->generate_item_id(0xFF);
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
|
||||
@@ -2196,54 +2267,12 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
res.item.id = l->generate_item_id(0xFF);
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load());
|
||||
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x00F);
|
||||
send_drop_item_to_lobby(l, res.item, cmd.rt_index != 0x30, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
if (res.is_from_rare_table) {
|
||||
for (auto lc : l->clients) {
|
||||
if (lc) {
|
||||
send_rare_notification_if_needed(lc, res.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid drop mode");
|
||||
}
|
||||
case Lobby::DropMode::SERVER_PRIVATE: {
|
||||
for (const auto& lc : l->clients) {
|
||||
if (lc && ((cmd.rt_index == 0x30) || (lc->floor == cmd.floor))) {
|
||||
auto res = generate_item();
|
||||
if (res.item.empty()) {
|
||||
l->log.info("No item was created for %s", lc->channel.name.c_str());
|
||||
} else {
|
||||
string name = s->describe_item(l->base_version, res.item, false);
|
||||
l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
res.item.id = l->generate_item_id(0xFF);
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
|
||||
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id));
|
||||
send_drop_item_to_channel(s, lc->channel, res.item, cmd.rt_index != 0x30, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
if (res.is_from_rare_table) {
|
||||
send_rare_notification_if_needed(lc, res.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw logic_error("invalid drop mode");
|
||||
}
|
||||
|
||||
if (cmd.rt_index == 0x30) {
|
||||
l->item_creator->set_box_destroyed(cmd.entity_id);
|
||||
} else {
|
||||
l->item_creator->set_monster_destroyed(cmd.entity_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2290,30 +2319,45 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
|
||||
if (is_v3(c->version())) {
|
||||
bool should_send_boss_drop_req = false;
|
||||
if (is_v3(c->version()) && (l->drop_mode != Lobby::DropMode::DISABLED)) {
|
||||
EnemyType boss_enemy_type = EnemyType::NONE;
|
||||
bool is_ep2 = (l->episode == Episode::EP2);
|
||||
if ((l->episode == Episode::EP1) && (c->floor == 0x0E)) {
|
||||
// On Normal, Dark Falz does not have a third phase, so send the drop
|
||||
// request after the end of the second phase. On all other difficulty
|
||||
// levels, send it after the third phase.
|
||||
if (((difficulty == 0) && (flag_index == 0x0035)) ||
|
||||
((difficulty != 0) && (flag_index == 0x0037))) {
|
||||
should_send_boss_drop_req = true;
|
||||
if ((difficulty == 0) && (flag_index == 0x0035)) {
|
||||
boss_enemy_type = EnemyType::DARK_FALZ_2;
|
||||
} else if ((difficulty != 0) && (flag_index == 0x0037)) {
|
||||
boss_enemy_type = EnemyType::DARK_FALZ_3;
|
||||
}
|
||||
} else if (is_ep2 && (flag_index == 0x0057) && (c->floor == 0x0D)) {
|
||||
should_send_boss_drop_req = true;
|
||||
boss_enemy_type = EnemyType::OLGA_FLOW_2;
|
||||
}
|
||||
|
||||
if (should_send_boss_drop_req) {
|
||||
auto c = l->clients.at(l->leader_id);
|
||||
if (c) {
|
||||
G_StandardDropItemRequest_PC_V3_BB_6x60 req = {
|
||||
if (boss_enemy_type != EnemyType::NONE) {
|
||||
l->log.info("Creating item from final boss (%s)", name_for_enum(boss_enemy_type));
|
||||
uint16_t enemy_id = 0xFFFF;
|
||||
if (l->map) {
|
||||
try {
|
||||
const auto& enemy = l->map->find_enemy(c->floor, boss_enemy_type);
|
||||
enemy_id = enemy.enemy_id;
|
||||
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);
|
||||
}
|
||||
l->log.info("Found enemy E-%hX on floor %" PRIX32, enemy_id, enemy.floor);
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Could not find enemy on floor %" PRIX32 "; unable to determine enemy type", c->floor);
|
||||
}
|
||||
}
|
||||
|
||||
if (boss_enemy_type != EnemyType::NONE) {
|
||||
G_StandardDropItemRequest_PC_V3_BB_6x60 drop_req = {
|
||||
{
|
||||
{0x60, 0x06, 0x0000},
|
||||
static_cast<uint8_t>(c->floor),
|
||||
static_cast<uint8_t>(is_ep2 ? 0x4E : 0x2F),
|
||||
0x0B4F,
|
||||
rare_table_index_for_enemy_type(boss_enemy_type),
|
||||
enemy_id == 0xFFFF ? 0x0B4F : enemy_id,
|
||||
is_ep2 ? -9999.0f : 10160.58984375f,
|
||||
0.0f,
|
||||
2,
|
||||
@@ -2321,7 +2365,7 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
|
||||
},
|
||||
0x01,
|
||||
{}};
|
||||
send_command_t(c, 0x62, l->leader_id, req);
|
||||
on_entity_drop_item_request(c, 0x62, l->leader_id, &drop_req, sizeof(drop_req));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,3 +246,38 @@ uint32_t default_specific_version_for_version(Version version, int64_t sub_versi
|
||||
return 0x00000000;
|
||||
}
|
||||
}
|
||||
|
||||
const char* file_path_token_for_version(Version version) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
return "dc-nte";
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return "dc-11-2000";
|
||||
case Version::DC_V1:
|
||||
return "dc-v1";
|
||||
case Version::DC_V2:
|
||||
return "dc-v2";
|
||||
case Version::PC_NTE:
|
||||
return "pc-nte";
|
||||
case Version::PC_V2:
|
||||
return "pc-v2";
|
||||
case Version::GC_NTE:
|
||||
return "gc-nte";
|
||||
case Version::GC_V3:
|
||||
return "gc-v3";
|
||||
case Version::GC_EP3_NTE:
|
||||
return "gc-ep3-nte";
|
||||
case Version::GC_EP3:
|
||||
return "gc-ep3";
|
||||
case Version::XB_V3:
|
||||
return "xb-v3";
|
||||
case Version::BB_V4:
|
||||
return "bb-v4";
|
||||
default:
|
||||
throw runtime_error("invalid game version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,3 +136,5 @@ template <>
|
||||
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior);
|
||||
template <>
|
||||
ServerBehavior enum_for_name<ServerBehavior>(const char* name);
|
||||
|
||||
const char* file_path_token_for_version(Version version);
|
||||
|
||||
Reference in New Issue
Block a user