add $whatobj command

This commit is contained in:
Martin Michelsen
2025-03-21 22:22:43 -07:00
parent ca1dc6ad7d
commit 69edba036e
12 changed files with 4083 additions and 8 deletions
+59
View File
@@ -2771,6 +2771,65 @@ ChatCommandDefinition cc_what(
},
unavailable_on_proxy_server);
ChatCommandDefinition cc_whatobj(
{"$whatobj"},
+[](const ServerArgs& a) -> void {
auto s = a.c->require_server_state();
auto l = a.c->require_lobby();
a.check_is_game(true);
if (!l->map_state) {
throw precondition_failed("$C4No map loaded");
}
// TODO: We should use the actual area if a loaded quest has reassigned
// them; it's likely that the variations will be wrong if we don't
auto sdt = s->set_data_table(a.c->version(), l->episode, l->mode, l->difficulty);
uint8_t area = sdt->default_area_for_floor(l->episode, a.c->floor);
uint8_t layout_var = (a.c->floor < 0x10) ? l->variations.entries[a.c->floor].layout.load() : 0x00;
float min_dist2 = 0.0f;
VectorXYZF nearest_worldspace_pos;
shared_ptr<const MapState::ObjectState> nearest_obj;
for (const auto& it : l->map_state->iter_object_states(a.c->version())) {
if (!it->super_obj || (it->super_obj->floor != a.c->floor)) {
continue;
}
const auto& def = it->super_obj->version(a.c->version());
if (!def.set_entry) {
continue;
}
VectorXYZF worldspace_pos;
try {
const auto& room = s->room_layout_index->get_room(area, layout_var, def.set_entry->room);
// This is the order in which the game does the rotations; not sure why
worldspace_pos = def.set_entry->pos.rotate_x(room.angle.x).rotate_z(room.angle.z).rotate_y(room.angle.y) + room.position;
} catch (const out_of_range&) {
a.c->log.warning("Can't find definition for room %02hhX:%02hhX:%08hX", area, layout_var, def.set_entry->room.load());
worldspace_pos = def.set_entry->pos;
}
float dist2 = (VectorXZF(worldspace_pos) - a.c->pos).norm2();
if (!nearest_obj || (dist2 < min_dist2)) {
nearest_obj = it;
nearest_worldspace_pos = worldspace_pos;
min_dist2 = dist2;
}
}
if (!nearest_obj) {
throw precondition_failed("$C4No objects nearby");
} else {
send_text_message_printf(a.c, "$C5K-%03zX\n$C6%s\nX:%.2f Z:%.2f",
nearest_obj->k_id, nearest_obj->type_name(a.c->version()),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.z.load());
auto set_str = nearest_obj->super_obj->version(a.c->version()).set_entry->str();
a.c->log.info("Object found via $whatobj: K-%03zX %s at x=%g y=%g z=%g",
nearest_obj->k_id, set_str.c_str(),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.y.load(), nearest_worldspace_pos.z.load());
}
},
unavailable_on_proxy_server);
ChatCommandDefinition cc_where(
{"$where"},
+[](const ServerArgs& a) -> void {
+35
View File
@@ -5,6 +5,10 @@
#include "Text.hh"
constexpr double radians_for_fixed_point_angle(uint16_t angle) {
return static_cast<double>(angle * 2 * M_PI) / 0x10000;
}
struct VectorXZF {
le_float x = 0.0;
le_float z = 0.0;
@@ -34,6 +38,12 @@ struct VectorXZF {
return ((this->x * this->x) + (this->z * this->z));
}
inline VectorXZF rotate_y(double angle) const {
double s = sin(angle);
double c = cos(angle);
return VectorXZF(this->x * c - this->z * s, this->x * s + this->z * c);
}
inline std::string str() const {
return phosg::string_printf("[VectorXZF x=%g z=%g]", this->x.load(), this->z.load());
}
@@ -73,6 +83,31 @@ struct VectorXYZF {
return ((this->x * this->x) + (this->y * this->y) + (this->z * this->z));
}
inline VectorXYZF rotate_x(double angle) const {
double s = sin(angle);
double c = cos(angle);
return VectorXYZF(
this->x,
this->y * c - this->z * s,
this->y * s + this->z * c);
}
inline VectorXYZF rotate_y(double angle) const {
double s = sin(angle);
double c = cos(angle);
return VectorXYZF(
this->x * c + this->z * s,
this->y,
-this->x * s + this->z * c);
}
inline VectorXYZF rotate_z(double angle) const {
double s = sin(angle);
double c = cos(angle);
return VectorXYZF(
this->x * c - this->y * s,
this->x * s + this->y * c,
this->z);
}
inline std::string str() const {
return phosg::string_printf("[VectorXYZF x=%g y=%g z=%g]", this->x.load(), this->y.load(), this->z.load());
}
+4
View File
@@ -315,6 +315,10 @@ void Lobby::load_maps() {
this->opt_rand_crypt,
s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations));
}
if (this->check_flag(Lobby::Flag::DEBUG)) {
this->map_state->print(stderr);
}
}
[[nodiscard]] bool Lobby::is_ep3_nte() const {
+30
View File
@@ -5418,3 +5418,33 @@ void MapState::print(FILE* stream) const {
fprintf(stream, " %s set_flags=%04hX\n", ev_str.c_str(), ev_st->flags);
}
}
static constexpr uint64_t room_layout_key(uint8_t area, uint8_t major_var, uint32_t room_id) {
return ((static_cast<uint64_t>(area) << 40) |
(static_cast<uint64_t>(major_var) << 32) |
static_cast<uint64_t>(room_id));
}
RoomLayoutIndex::RoomLayoutIndex(const phosg::JSON& json) {
for (const auto& [area_and_major_var, rooms_json] : json.as_dict()) {
auto tokens = phosg::split(area_and_major_var, '-');
uint8_t area = stoul(tokens.at(0), nullptr, 16);
uint8_t major_var = stoul(tokens.at(1), nullptr, 16);
for (const auto& [room_id_str, room_json] : rooms_json->as_dict()) {
uint32_t room_id = stoul(room_id_str, nullptr, 16);
auto emplace_ret = this->rooms.emplace(room_layout_key(area, major_var, room_id), Room());
auto& room = emplace_ret.first->second;
const auto& l = room_json->as_list();
room.position.x = l.at(0)->as_float();
room.position.y = l.at(1)->as_float();
room.position.z = l.at(2)->as_float();
room.angle.x = l.at(3)->as_int();
room.angle.y = l.at(4)->as_int();
room.angle.z = l.at(5)->as_int();
}
}
}
const RoomLayoutIndex::Room& RoomLayoutIndex::get_room(uint8_t area, uint8_t major_var, uint32_t room_id) const {
return this->rooms.at(room_layout_key(area, major_var, room_id));
}
+19 -1
View File
@@ -173,7 +173,14 @@ public:
/* 0A */ le_uint16_t group = 0;
/* 0C */ le_uint16_t room = 0;
/* 0E */ le_uint16_t unknown_a3 = 0;
// The position is relative to the room in which the object is placed; to
// get the actual world position, the object's position must be rotated
// around the room's origin by the room's angles, then translated by the
// room's offset. The room's angle and offset can be found in the area's
// n.rel file.
/* 10 */ VectorXYZF pos;
// Angles are specified as 16-bit integers, where 0 is no rotation around
// the axis and FFFF is almost a complete counterclockwise rotation.
/* 1C */ VectorXYZI angle;
/* 28 */ le_float fparam1 = 0.0f; // Boxes: if <= 0, this is a specialized box, and the specialization is in param4/5/6
/* 2C */ le_float fparam2 = 0.0f;
@@ -697,7 +704,7 @@ public:
inline const char* type_name(Version v) const {
return this->super_obj
? MapFile::name_for_object_type(this->super_obj->version(v).set_entry->base_type)
: "<PLAYER TRAP>";
: "<DYNAMIC>";
}
};
@@ -984,3 +991,14 @@ public:
void verify() const;
void print(FILE* stream) const;
};
struct RoomLayoutIndex {
struct Room {
VectorXYZF position;
VectorXYZI angle;
};
std::unordered_map<uint64_t, Room> rooms;
explicit RoomLayoutIndex(const phosg::JSON& json);
const Room& get_room(uint8_t area, uint8_t major_var, uint32_t room_id) const;
};
+1 -1
View File
@@ -4391,7 +4391,7 @@ shared_ptr<Lobby> create_game_generic(
break;
}
if (creator_c->login->account->check_flag(Account::Flag::DEBUG)) {
if (creator_c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
game->set_flag(Lobby::Flag::DEBUG);
}
if (creator_c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION)) {
+21 -2
View File
@@ -1568,10 +1568,27 @@ void ServerState::load_patch_indexes(bool from_non_event_thread) {
void ServerState::load_maps(bool from_non_event_thread) {
using SDT = SetDataTable;
config_log.info("Loading free play map files");
config_log.info("Loading map layouts");
auto room_layout_index = make_shared<RoomLayoutIndex>(
phosg::JSON::parse(phosg::load_file("system/maps/room-layout-index.json")));
config_log.info("Loading Episode 3 Morgue maps");
unordered_map<uint64_t, shared_ptr<const MapFile>> new_map_file_for_source_hash;
map<uint32_t, array<shared_ptr<const MapFile>, NUM_VERSIONS>> new_map_files_for_free_play_key;
{
// TODO: Ep3 NTE loads map_city00_on, but it appears there are some
// variants. Figure this out and load those maps too.
auto objects_data = this->load_map_file(Version::GC_EP3, "system/maps/gc-ep3/map_city_on_battle_o.dat");
auto enemies_data = this->load_map_file(Version::GC_EP3, "system/maps/gc-ep3/map_city_on_battle_e.dat");
if (objects_data || enemies_data) {
uint32_t free_play_key = this->free_play_key(Episode::EP3, GameMode::NORMAL, 0, 0, 0, 0);
auto map_file = make_shared<MapFile>(0, objects_data, enemies_data, nullptr);
new_map_file_for_source_hash.emplace(map_file->source_hash(), map_file);
new_map_files_for_free_play_key[free_play_key].at(static_cast<size_t>(Version::GC_EP3)) = map_file;
}
}
config_log.info("Loading free play map files");
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
for (Episode episode : episodes) {
@@ -1656,9 +1673,11 @@ void ServerState::load_maps(bool from_non_event_thread) {
auto set = [s = this->shared_from_this(),
new_map_file_for_source_hash = std::move(new_map_file_for_source_hash),
new_map_files_for_free_play_key = std::move(new_map_files_for_free_play_key)]() {
new_map_files_for_free_play_key = std::move(new_map_files_for_free_play_key),
new_room_layout_index = std::move(room_layout_index)]() {
s->map_file_for_source_hash = std::move(new_map_file_for_source_hash);
s->map_files_for_free_play_key = std::move(new_map_files_for_free_play_key);
s->room_layout_index = new_room_layout_index;
s->supermap_for_source_hash_sum.clear();
s->supermap_for_free_play_key.clear();
};
+1
View File
@@ -177,6 +177,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::map<uint32_t, std::array<std::shared_ptr<const MapFile>, NUM_VERSIONS>> map_files_for_free_play_key;
std::unordered_map<uint64_t, std::shared_ptr<const SuperMap>> supermap_for_source_hash_sum;
std::unordered_map<uint32_t, std::shared_ptr<const SuperMap>> supermap_for_free_play_key;
std::shared_ptr<const RoomLayoutIndex> room_layout_index;
std::shared_ptr<FileContentsCache> bb_stream_files_cache;
std::shared_ptr<FileContentsCache> bb_system_cache;
std::shared_ptr<FileContentsCache> gba_files_cache;