add $whatobj command
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user