fix game flag translation across v2/v3 boundary

This commit is contained in:
Martin Michelsen
2025-05-28 21:36:05 -07:00
parent e72e37f713
commit aabbafb749
25 changed files with 95 additions and 125 deletions
+1 -3
View File
@@ -4982,9 +4982,7 @@ struct G_6x70_Base_DCNTE {
/* 0002 */ le_uint16_t room_id = 0;
/* 0004 */ le_uint32_t flags1 = 0;
/* 0008 */ VectorXYZF pos;
/* 0014 */ le_uint32_t angle_x = 0;
/* 0018 */ le_uint32_t angle_y = 0;
/* 001C */ le_uint32_t angle_z = 0;
/* 0014 */ VectorXYZI angle;
/* 0020 */ le_uint16_t unknown_a3a = 0;
/* 0022 */ le_uint16_t current_hp = 0;
} __packed_ws__(G_6x70_Base_DCNTE, 0x24);
+17
View File
@@ -28,6 +28,23 @@ static void set_log_level_from_json(
}
}
void set_all_log_levels(phosg::LogLevel level) {
channel_exceptions_log.min_level = level;
client_log.min_level = level;
command_data_log.min_level = level;
config_log.min_level = level;
dns_server_log.min_level = level;
function_compiler_log.min_level = level;
ip_stack_simulator_log.min_level = level;
lobby_log.min_level = level;
patch_index_log.min_level = level;
player_data_log.min_level = level;
proxy_server_log.min_level = level;
replay_log.min_level = level;
server_log.min_level = level;
static_game_data_log.min_level = level;
}
void set_log_levels_from_json(const phosg::JSON& json) {
set_log_level_from_json(channel_exceptions_log, json, "ChannelExceptions");
set_log_level_from_json(client_log, json, "Clients");
+1
View File
@@ -18,4 +18,5 @@ extern phosg::PrefixedLogger replay_log;
extern phosg::PrefixedLogger server_log;
extern phosg::PrefixedLogger static_game_data_log;
void set_all_log_levels(phosg::LogLevel level);
void set_log_levels_from_json(const phosg::JSON& json);
+3
View File
@@ -3195,6 +3195,9 @@ Action a_run_server_replay_log(
}
auto state = make_shared<ServerState>(get_config_filename(args));
if (args.get<bool>("debug")) {
state->is_debug = true;
}
state->load_all(true);
if (state->dns_server_port) {
+7 -62
View File
@@ -5848,55 +5848,12 @@ uint32_t MapState::RareEnemyRates::for_enemy_type(EnemyType type) const {
}
}
const shared_ptr<const MapState::RareEnemyRates> MapState::NO_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(
0, 0, 0);
const shared_ptr<const MapState::RareEnemyRates> MapState::NO_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(0, 0, 0);
const shared_ptr<const MapState::RareEnemyRates> MapState::DEFAULT_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(
MapState::RareEnemyRates::DEFAULT_RARE_ENEMY_RATE_V3,
MapState::RareEnemyRates::DEFAULT_MERICARAND_RATE_V3,
MapState::RareEnemyRates::DEFAULT_RARE_BOSS_RATE_V4);
uint32_t MapState::EnemyState::convert_game_flags(uint32_t game_flags, bool to_v3) {
// The format of game_flags was changed significantly between v2 and v3, and
// not accounting for this results in odd effects like other characters not
// appearing when joining a game. Unfortunately, some bits were deleted on v3
// and other bits were added, so it doesn't suffice to simply store the most
// complete format of this field - we have to be able to convert between the
// two.
// Bits on v2: ?IHCBAzy xwvutsrq ponmlkji hgfedcba
// Bits on v3: ?IHGFEDC BAzyxwvu srqponkj hgfedcba
// The bits ilmt were removed in v3 and the bits to their left were shifted
// right. The bits DEFG were added in v3 and do not exist on v2.
// Known meanings for these bits:
// o = is dead
// n = should play hit animation
// y = is near enemy
// H = is enemy?
// I = is object? (some entities have both H and I set though)
// TODO: The above might all be wrong.
// GC 00100000 10010000 00001110 00000000
// PC 00101001 00000000 01100100 00000000
// PC 00101001 10110000 00101110 00000000
// GC 00100000 10011011 00000111 00000000
// PC 00101001 10010000 00101110 00000000
// GC 00100000 10011001 00000111 00000000
if (to_v3) {
return (game_flags & 0xE00000FF) |
((game_flags & 0x00000600) >> 1) |
((game_flags & 0x0007E000) >> 3) |
((game_flags & 0x1FF00000) >> 4);
} else {
return (game_flags & 0xE00000FF) |
((game_flags << 1) & 0x00000600) |
((game_flags << 3) & 0x0007E000) |
((game_flags << 4) & 0x1FF00000);
}
}
MapState::EntityIterator::EntityIterator(MapState* map_state, Version version, bool at_end)
: map_state(map_state),
version(version),
@@ -6413,7 +6370,6 @@ void MapState::import_object_states_from_sync(
void MapState::import_enemy_states_from_sync(Version from_version, const SyncEnemyStateEntry* entries, size_t entry_count) {
this->log.info_f("Importing enemy state from sync command");
size_t enemy_index = 0;
bool is_v3 = !is_v1_or_v2(from_version);
for (const auto& fc : this->floor_config_entries) {
if (!fc.super_map) {
continue;
@@ -6434,15 +6390,10 @@ void MapState::import_enemy_states_from_sync(Version from_version, const SyncEne
if (ene_st->super_ene != ene) {
throw logic_error("super enemy link is incorrect");
}
if (ene_st->get_game_flags(is_v3) != entry.flags) {
this->log.warning_f("({:04X} => E-{:03X}) Flags from client ({:08X}({})) do not match game flags from map ({:08X}({}))",
enemy_index,
ene_st->e_id,
entry.flags,
is_v3 ? "v3" : "v2",
ene_st->game_flags,
(ene_st->server_flags & MapState::EnemyState::Flag::GAME_FLAGS_IS_V3) ? "v3" : "v2");
ene_st->set_game_flags(entry.flags, !is_v1_or_v2(from_version));
if (ene_st->game_flags != entry.flags) {
this->log.warning_f("({:04X} => E-{:03X}) Flags from client ({:08X}) do not match game flags from map ({:08X})",
enemy_index, ene_st->e_id, entry.flags, ene_st->game_flags);
ene_st->game_flags = entry.flags;
}
if (ene_st->total_damage != entry.total_damage) {
this->log.warning_f("({:04X} => E-{:03X}) Total damage from client ({}) does not match total damage from map ({})",
@@ -6771,14 +6722,8 @@ void MapState::print(FILE* stream) const {
}
}
string ene_str = ene_st->super_ene->str();
phosg::fwrite_fmt(stream, " {} total_damage={:04X} rare_flags={:04X} game_flags={:08X}({}) set_flags={:04X} server_flags={:04X}\n",
ene_str,
ene_st->total_damage,
ene_st->rare_flags,
ene_st->game_flags,
(ene_st->server_flags & MapState::EnemyState::Flag::GAME_FLAGS_IS_V3) ? "v3" : "v2",
ene_st->set_flags,
ene_st->server_flags);
phosg::fwrite_fmt(stream, " {} total_damage={:04X} rare_flags={:04X} game_flags={:08X} set_flags={:04X} server_flags={:04X}\n",
ene_str, ene_st->total_damage, ene_st->rare_flags, ene_st->game_flags, ene_st->set_flags, ene_st->server_flags);
}
if (this->bb_rare_enemy_indexes.empty()) {
-20
View File
@@ -716,7 +716,6 @@ public:
ITEM_DROPPED = 0x0008,
ALL_HITS_MASK_FIRST = 0x0010,
ALL_HITS_MASK = 0x00F0,
GAME_FLAGS_IS_V3 = 0x0100,
};
size_t e_id = 0;
size_t set_id = 0;
@@ -727,8 +726,6 @@ public:
uint16_t set_flags = 0; // Only used if super_ene->child_index == 0
uint16_t server_flags = 0;
static uint32_t convert_game_flags(uint32_t game_flags, bool to_v3);
inline void reset() {
this->total_damage = 0;
this->rare_flags = 0;
@@ -737,23 +734,6 @@ public:
this->server_flags = 0;
}
inline void set_game_flags(uint32_t game_flags, bool is_v3) {
this->game_flags = game_flags;
if (is_v3) {
this->server_flags |= Flag::GAME_FLAGS_IS_V3;
} else {
this->server_flags &= ~Flag::GAME_FLAGS_IS_V3;
}
}
inline uint32_t get_game_flags(bool is_v3) const {
bool flags_is_v3 = (this->server_flags & Flag::GAME_FLAGS_IS_V3);
if (flags_is_v3 == is_v3) {
return this->game_flags;
} else {
return this->convert_game_flags(this->game_flags, is_v3);
}
}
inline bool is_rare(Version version) const {
return (((this->rare_flags >> static_cast<size_t>(version)) & 1) ||
((version == Version::BB_V4) ? this->super_ene->is_default_rare_bb : this->super_ene->is_default_rare_v123));
+41 -21
View File
@@ -1154,10 +1154,42 @@ G_6x70_Base_V1 Parsed6x70Data::base_v1(bool is_v3) const {
return ret;
}
uint32_t Parsed6x70Data::convert_game_flags(uint32_t game_flags, bool to_v3) {
// The format of game_flags for players was changed significantly between v2
// and v3, and not accounting for this results in odd effects like other
// characters not appearing when joining a game. Unfortunately, some bits
// were deleted on v3 and other bits were added, so it doesn't suffice to
// simply store the most complete format of this field - we have to be able
// to convert between the two.
// Bits on v2: ?IHCBAzy xwvutsrq ponmlkji hgfedcba
// Bits on v3: ?IHGFEDC BAzyxwvu srqponkj hgfedcba
// The bits ilmt were removed in v3 and the bits to their left were shifted
// right. The bits DEFG were added in v3 and do not exist on v2.
// Known meanings for these bits:
// o = is dead
// n = should play hit animation
// y = is near enemy
// H = is enemy?
// I = is object? (some entities have both H and I set though)
if (to_v3) {
return (game_flags & 0xE00000FF) |
((game_flags & 0x00000600) >> 1) |
((game_flags & 0x0007E000) >> 3) |
((game_flags & 0x1FF00000) >> 4);
} else {
return (game_flags & 0xE00000FF) |
((game_flags << 1) & 0x00000600) |
((game_flags << 3) & 0x0007E000) |
((game_flags << 4) & 0x1FF00000);
}
}
uint32_t Parsed6x70Data::get_game_flags(bool is_v3) const {
return (this->game_flags_is_v3 == is_v3)
? this->game_flags
: MapState::EnemyState::convert_game_flags(this->game_flags, is_v3);
: Parsed6x70Data::convert_game_flags(this->game_flags, is_v3);
}
static asio::awaitable<void> on_sync_joining_player_disp_and_inventory(
@@ -3423,36 +3455,29 @@ static asio::awaitable<void> on_update_enemy_state(shared_ptr<Client> c, Subcomm
if ((cmd.enemy_index & 0xF000) || (cmd.header.entity_id != (cmd.enemy_index | 0x1000))) {
throw runtime_error("mismatched enemy id/index");
}
bool is_v3 = !is_v1_or_v2(c->version());
auto ene_st = l->map_state->enemy_state_for_index(c->version(), c->floor, cmd.enemy_index);
uint32_t src_flags = is_big_endian(c->version()) ? bswap32(cmd.game_flags) : cmd.game_flags.load();
if (l->difficulty == 3) {
src_flags = (src_flags & 0xFFFFFFC0) | (ene_st->get_game_flags(is_v3) & 0x0000003F);
src_flags = (src_flags & 0xFFFFFFC0) | (ene_st->game_flags & 0x0000003F);
}
ene_st->set_game_flags(src_flags, is_v3);
ene_st->game_flags = src_flags;
ene_st->total_damage = cmd.total_damage;
ene_st->set_last_hit_by_client_id(c->lobby_client_id);
l->log.info_f("E-{:03X} updated to damage={} game_flags={:08X} ({})",
ene_st->e_id,
ene_st->total_damage,
ene_st->game_flags,
(ene_st->server_flags & MapState::EnemyState::Flag::GAME_FLAGS_IS_V3) ? "v3" : "v2");
l->log.info_f("E-{:03X} updated to damage={} game_flags={:08X}", ene_st->e_id, ene_st->total_damage, ene_st->game_flags);
for (auto lc : l->clients) {
if (lc && (lc != c)) {
cmd.enemy_index = l->map_state->index_for_enemy_state(lc->version(), ene_st);
if (cmd.enemy_index != 0xFFFF) {
cmd.header.entity_id = 0x1000 | cmd.enemy_index;
uint32_t game_flags = ene_st->get_game_flags(!is_v1_or_v2(lc->version()));
cmd.game_flags = is_big_endian(lc->version()) ? phosg::bswap32(game_flags) : game_flags;
cmd.game_flags = is_big_endian(lc->version()) ? phosg::bswap32(ene_st->game_flags) : ene_st->game_flags;
send_command_t(lc, 0x60, 0x00, cmd);
}
}
}
}
static asio::awaitable<void> on_set_enemy_low_game_flags_ultimate(
shared_ptr<Client> c, SubcommandMessage& msg) {
static asio::awaitable<void> on_set_enemy_low_game_flags_ultimate(shared_ptr<Client> c, SubcommandMessage& msg) {
auto& cmd = msg.check_size_t<G_SetEnemyLowGameFlagsUltimate_6x9C>();
if (command_is_private(msg.command) ||
@@ -3467,15 +3492,10 @@ static asio::awaitable<void> on_set_enemy_low_game_flags_ultimate(
co_return;
}
bool is_v3 = !is_v1_or_v2(c->version());
auto ene_st = l->map_state->enemy_state_for_index(c->version(), c->floor, cmd.header.entity_id - 0x1000);
uint32_t game_flags = ene_st->get_game_flags(is_v3);
if (!(game_flags & cmd.low_game_flags)) {
ene_st->set_game_flags(game_flags | cmd.low_game_flags, is_v3);
l->log.info_f("E-{:03X} updated to game_flags={:08X} ({})",
ene_st->e_id,
ene_st->game_flags,
(ene_st->server_flags & MapState::EnemyState::Flag::GAME_FLAGS_IS_V3) ? "v3" : "v2");
if (!(ene_st->game_flags & cmd.low_game_flags)) {
ene_st->game_flags |= cmd.low_game_flags;
l->log.info_f("E-{:03X} updated to game_flags={:08X}", ene_st->e_id, ene_st->game_flags);
}
co_await forward_subcommand_with_entity_id_transcode_t<G_SetEnemyLowGameFlagsUltimate_6x9C>(c, msg);
+1
View File
@@ -121,6 +121,7 @@ protected:
Version from_version,
bool from_client_customization);
G_6x70_Base_V1 base_v1(bool is_v3) const;
static uint32_t convert_game_flags(uint32_t game_flags, bool to_v3);
uint32_t get_game_flags(bool is_v3) const;
};
+1 -2
View File
@@ -2786,10 +2786,9 @@ void send_game_enemy_state(shared_ptr<Client> c) {
auto s = c->require_server_state();
vector<SyncEnemyStateEntry> entries;
bool is_v3 = !is_v1_or_v2(c->version());
for (auto ene_st : l->map_state->iter_enemy_states(c->version())) {
auto& entry = entries.emplace_back();
entry.flags = ene_st->get_game_flags(is_v3);
entry.flags = ene_st->game_flags;
entry.item_drop_id = (ene_st->server_flags & MapState::EnemyState::Flag::ITEM_DROPPED)
? 0xFFFF
: (0xCA0 + l->map_state->index_for_enemy_state(c->version(), ene_st));
+5 -1
View File
@@ -1059,7 +1059,11 @@ void ServerState::load_config_early() {
this->exp_share_multiplier = this->config_json->get_float("BBEXPShareMultiplier", 0.5);
this->server_global_drop_rate_multiplier = this->config_json->get_float("ServerGlobalDropRateMultiplier", 1);
set_log_levels_from_json(this->config_json->get("LogLevels", phosg::JSON::dict()));
if (this->is_debug) {
set_all_log_levels(phosg::LogLevel::L_DEBUG);
} else {
set_log_levels_from_json(this->config_json->get("LogLevels", phosg::JSON::dict()));
}
try {
this->run_shell_behavior = this->config_json->at("RunInteractiveShell").as_bool()
+1
View File
@@ -109,6 +109,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
uint64_t client_ping_interval_usecs = 30000000;
uint64_t client_idle_timeout_usecs = 60000000;
uint64_t patch_client_idle_timeout_usecs = 300000000;
bool is_debug = false;
bool ip_stack_debug = false;
bool allow_unregistered_users = false;
bool allow_pc_nte = false;