diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index e12b8b043..ed47890f7 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -198,6 +198,14 @@ void ServerConnection::connection_ostream::flush_proxy() void ServerConnection::threadFn(void *arg) { ServerConnection *me = (ServerConnection*)arg; + + me->threadFn(); + + delete me; +} + +void ServerConnection::threadFn() +{ color_ostream_proxy out(Core::getInstance().getConsole()); /* Handshake */ @@ -205,10 +213,9 @@ void ServerConnection::threadFn(void *arg) { RPCHandshakeHeader header; - if (!readFullBuffer(me->socket, &header, sizeof(header))) + if (!readFullBuffer(socket, &header, sizeof(header))) { out << "In RPC server: could not read handshake header." << endl; - delete me; return; } @@ -216,17 +223,15 @@ void ServerConnection::threadFn(void *arg) header.version != 1) { out << "In RPC server: invalid handshake header." << endl; - delete me; return; } memcpy(header.magic, RPCHandshakeHeader::RESPONSE_MAGIC, sizeof(header.magic)); header.version = 1; - if (me->socket->Send((uint8*)&header, sizeof(header)) != sizeof(header)) + if (socket->Send((uint8*)&header, sizeof(header)) != sizeof(header)) { out << "In RPC server: could not send handshake response." << endl; - delete me; return; } } @@ -235,11 +240,11 @@ void ServerConnection::threadFn(void *arg) std::cerr << "Client connection established." << endl; - while (!me->in_error) { + while (!in_error) { // Read the message RPCMessageHeader header; - if (!readFullBuffer(me->socket, &header, sizeof(header))) + if (!readFullBuffer(socket, &header, sizeof(header))) { out.printerr("In RPC server: I/O error in receive header.\n"); break; @@ -256,7 +261,7 @@ void ServerConnection::threadFn(void *arg) std::auto_ptr buf(new uint8_t[header.size]); - if (!readFullBuffer(me->socket, buf.get(), header.size)) + if (!readFullBuffer(socket, buf.get(), header.size)) { out.printerr("In RPC server: I/O error in receive %d bytes of data.\n", header.size); break; @@ -267,31 +272,40 @@ void ServerConnection::threadFn(void *arg) // Find and call the function int in_size = header.size; - ServerFunctionBase *fn = vector_get(me->functions, header.id); + ServerFunctionBase *fn = vector_get(functions, header.id); MessageLite *reply = NULL; command_result res = CR_FAILURE; if (!fn) { - me->stream.printerr("RPC call of invalid id %d\n", header.id); + stream.printerr("RPC call of invalid id %d\n", header.id); } else { if (!fn->in()->ParseFromArray(buf.get(), header.size)) { - me->stream.printerr("In call to %s: could not decode input args.\n", fn->name); + stream.printerr("In call to %s: could not decode input args.\n", fn->name); } else { buf.reset(); reply = fn->out(); - res = fn->execute(me->stream); + + if (fn->flags & SF_DONT_SUSPEND) + { + res = fn->execute(stream); + } + else + { + CoreSuspender suspend; + res = fn->execute(stream); + } } } // Flush all text output - if (me->in_error) + if (in_error) break; //out.print("Answer %d:%d\n", res, reply); @@ -301,16 +315,16 @@ void ServerConnection::threadFn(void *arg) if (out_size > RPCMessageHeader::MAX_MESSAGE_SIZE) { - me->stream.printerr("In call to %s: reply too large: %d.\n", + stream.printerr("In call to %s: reply too large: %d.\n", (fn ? fn->name : "UNKNOWN"), out_size); res = CR_LINK_FAILURE; } - me->stream.flush(); + stream.flush(); if (res == CR_OK && reply) { - if (!sendRemoteMessage(me->socket, RPC_REPLY_RESULT, reply, true)) + if (!sendRemoteMessage(socket, RPC_REPLY_RESULT, reply, true)) { out.printerr("In RPC server: I/O error in send result.\n"); break; @@ -321,7 +335,7 @@ void ServerConnection::threadFn(void *arg) header.id = RPC_REPLY_FAIL; header.size = res; - if (me->socket->Send((uint8_t*)&header, sizeof(header)) != sizeof(header)) + if (socket->Send((uint8_t*)&header, sizeof(header)) != sizeof(header)) { out.printerr("In RPC server: I/O error in send failure code.\n"); break; @@ -337,8 +351,6 @@ void ServerConnection::threadFn(void *arg) } std::cerr << "Shutting down client connection." << endl; - - delete me; } ServerMain::ServerMain() diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 1fdd26c74..68f29e4f8 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -48,13 +48,18 @@ POSSIBILITY OF SUCH DAMAGE. #include "RemoteTools.h" #include "PluginManager.h" #include "MiscUtils.h" +#include "VersionInfo.h" #include "modules/Materials.h" #include "modules/Translation.h" #include "modules/Units.h" +#include "modules/World.h" #include "DataDefs.h" #include "df/ui.h" +#include "df/ui_advmode.h" +#include "df/world.h" +#include "df/world_data.h" #include "df/unit.h" #include "df/unit_soul.h" #include "df/unit_skill.h" @@ -63,10 +68,12 @@ POSSIBILITY OF SUCH DAMAGE. #include "df/inorganic_raw.h" #include "df/creature_raw.h" #include "df/plant_raw.h" +#include "df/nemesis_record.h" #include "df/historical_figure.h" #include "df/historical_entity.h" #include "df/squad.h" #include "df/squad_position.h" +#include "df/death_info.h" #include "BasicApi.pb.h" @@ -94,11 +101,13 @@ void DFHack::describeEnum(RepeatedPtrField *pf, int base, { for (int i = 0; i < size; i++) { + const char *key = names[i]; + if (!key) + continue; + auto item = pf->Add(); item->set_value(base+i); - const char *key = names[i]; - if (key) - item->set_name(key); + item->set_name(key); } } @@ -107,11 +116,16 @@ void DFHack::describeBitfield(RepeatedPtrField *pf, { for (int i = 0; i < size; i++) { + const char *key = items[i].name; + if (!key && items[i].size <= 1) + continue; + auto item = pf->Add(); + item->set_value(i); - const char *key = items[i].name; if (key) item->set_name(key); + if (items[i].size > 1) { item->set_bit_size(items[i].size); @@ -217,11 +231,22 @@ void DFHack::describeName(NameInfo *info, df::language_name *name) std::string lname = Translation::TranslateName(name, false, true); if (!lname.empty()) info->set_last_name(lname); + lname = Translation::TranslateName(name, true, true); if (!lname.empty()) info->set_english_name(lname); } +void DFHack::describeNameTriple(NameTriple *info, const std::string &name, + const std::string &plural, const std::string &adj) +{ + info->set_normal(name); + if (!plural.empty() && plural != name) + info->set_plural(plural); + if (!adj.empty() && adj != name) + info->set_adjective(adj); +} + void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, const BasicUnitInfoMask *mask) { @@ -249,6 +274,27 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, if (unit->hist_figure_id >= 0) info->set_histfig_id(unit->hist_figure_id); + if (unit->counters.death_id >= 0) + { + info->set_death_id(unit->counters.death_id); + if (auto death = df::death_info::find(unit->counters.death_id)) + info->set_death_flags(death->flags.whole); + } + + if (mask && mask->profession()) + { + if (unit->profession >= 0) + info->set_profession(unit->profession); + if (!unit->custom_profession.empty()) + info->set_custom_profession(unit->custom_profession); + + if (unit->military.squad_index >= 0) + { + info->set_squad_id(unit->military.squad_index); + info->set_squad_position(unit->military.squad_position); + } + } + if (mask && mask->labors()) { for (int i = 0; i < sizeof(unit->status.labors)/sizeof(bool); i++) @@ -269,6 +315,106 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, item->set_experience(skill->experience); } } + + if (unit->curse.add_tags1.whole || + unit->curse.add_tags2.whole || + unit->curse.rem_tags1.whole || + unit->curse.rem_tags2.whole || + unit->curse.name_visible) + { + auto curse = info->mutable_curse(); + + curse->set_add_tags1(unit->curse.add_tags1.whole); + curse->set_rem_tags1(unit->curse.rem_tags1.whole); + curse->set_add_tags2(unit->curse.add_tags2.whole); + curse->set_rem_tags2(unit->curse.rem_tags2.whole); + + if (unit->curse.name_visible) + describeNameTriple(curse->mutable_name(), unit->curse.name, + unit->curse.name_plural, unit->curse.name_adjective); + } + + for (size_t i = 0; i < unit->burrows.size(); i++) + info->add_burrows(unit->burrows[i]); +} + +static command_result GetVersion(color_ostream &stream, + const EmptyMessage *, StringMessage *out) +{ + out->set_value(DFHACK_VERSION); + return CR_OK; +} + +static command_result GetDFVersion(color_ostream &stream, + const EmptyMessage *, StringMessage *out) +{ + out->set_value(Core::getInstance().vinfo->getVersion()); + return CR_OK; +} + +static command_result GetWorldInfo(color_ostream &stream, + const EmptyMessage *, GetWorldInfoOut *out) +{ + using df::global::ui; + using df::global::ui_advmode; + using df::global::world; + + if (!ui || !world || !Core::getInstance().isWorldLoaded()) + return CR_NOT_FOUND; + + t_gamemodes mode; + if (!Core::getInstance().getWorld()->ReadGameMode(mode)) + mode.g_type = GAMETYPE_DWARF_MAIN; + + out->set_save_dir(world->cur_savegame.save_dir); + + if (world->world_data->name.has_name) + describeName(out->mutable_world_name(), &world->world_data->name); + + switch (mode.g_type) + { + case GAMETYPE_DWARF_MAIN: + case GAMETYPE_DWARF_RECLAIM: + out->set_mode(GetWorldInfoOut::MODE_DWARF); + out->set_civ_id(ui->civ_id); + out->set_site_id(ui->site_id); + out->set_group_id(ui->group_id); + out->set_race_id(ui->race_id); + break; + + case GAMETYPE_ADVENTURE_MAIN: + out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); + + if (auto unit = vector_get(world->units.other[0], 0)) + out->set_player_unit_id(unit->id); + + if (!ui_advmode) + break; + + if (auto nemesis = vector_get(world->nemesis.all, ui_advmode->player_id)) + { + if (nemesis->figure) + out->set_player_histfig_id(nemesis->figure->id); + + for (size_t i = 0; i < nemesis->companions.size(); i++) + { + auto unm = df::nemesis_record::find(nemesis->companions[i]); + if (!unm || !unm->figure) + continue; + out->add_companion_histfig_ids(unm->figure->id); + } + } + break; + + case GAMETYPE_VIEW_LEGENDS: + out->set_mode(GetWorldInfoOut::MODE_LEGENDS); + break; + + default: + return CR_NOT_FOUND; + } + + return CR_OK; } static command_result ListEnums(color_ostream &stream, @@ -276,13 +422,24 @@ static command_result ListEnums(color_ostream &stream, { #define ENUM(name) describe_enum(out->mutable_##name()); #define BITFIELD(name) describe_bitfield(out->mutable_##name()); + ENUM(material_flags); ENUM(inorganic_flags); + BITFIELD(unit_flags1); BITFIELD(unit_flags2); BITFIELD(unit_flags3); + ENUM(unit_labor); ENUM(job_skill); + + BITFIELD(cie_add_tag_mask1); + BITFIELD(cie_add_tag_mask2); + + describe_bitfield(out->mutable_death_info_flags()); + + ENUM(profession); + #undef ENUM #undef BITFIELD } @@ -297,8 +454,6 @@ static void listMaterial(ListMaterialsOut *out, int type, int index, const Basic static command_result ListMaterials(color_ostream &stream, const ListMaterialsIn *in, ListMaterialsOut *out) { - CoreSuspender suspend; - auto mask = in->has_mask() ? &in->mask() : NULL; for (int i = 0; i < in->id_list_size(); i++) @@ -350,8 +505,6 @@ static command_result ListMaterials(color_ostream &stream, static command_result ListUnits(color_ostream &stream, const ListUnitsIn *in, ListUnitsOut *out) { - CoreSuspender suspend; - auto mask = in->has_mask() ? &in->mask() : NULL; if (in->id_list_size() > 0) @@ -363,7 +516,8 @@ static command_result ListUnits(color_ostream &stream, describeUnit(out->add_value(), unit, mask); } } - else + + if (in->scan_all()) { auto &vec = df::unit::get_vector(); @@ -373,7 +527,13 @@ static command_result ListUnits(color_ostream &stream, if (in->has_race() && unit->race != in->race()) continue; - if (in->civ_id() && unit->civ_id != in->civ_id()) + if (in->has_civ_id() && unit->civ_id != in->civ_id()) + continue; + if (in->has_dead() && Units::isDead(unit) != in->dead()) + continue; + if (in->has_alive() && Units::isAlive(unit) != in->alive()) + continue; + if (in->has_sane() && Units::isSane(unit) != in->sane()) continue; describeUnit(out->add_value(), unit, mask); @@ -415,14 +575,21 @@ CoreService::CoreService() { suspend_depth = 0; // These 2 methods must be first, so that they get id 0 and 1 - addMethod("BindMethod", &CoreService::BindMethod); - addMethod("RunCommand", &CoreService::RunCommand); + addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND); + addMethod("RunCommand", &CoreService::RunCommand, SF_DONT_SUSPEND); // Add others here: - addMethod("CoreSuspend", &CoreService::CoreSuspend); - addMethod("CoreResume", &CoreService::CoreResume); + addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND); + addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND); + + // Functions: + addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND); + addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND); + + addFunction("GetWorldInfo", GetWorldInfo); + + addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND); - addFunction("ListEnums", ListEnums, SF_CALLED_ONCE); addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); addFunction("ListUnits", ListUnits); addFunction("ListSquads", ListSquads); diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index d7b216f7f..d89b44959 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -43,7 +43,10 @@ namespace DFHack enum ServerFunctionFlags { // The function is expected to be called only once per client, // so always delete all cached buffers after processing. - SF_CALLED_ONCE = 1 + SF_CALLED_ONCE = 1, + // Don't automatically suspend the core around the call. + // The function is supposed to manage locking itself. + SF_DONT_SUSPEND = 2 }; class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase { @@ -225,6 +228,7 @@ namespace DFHack tthread::thread *thread; static void threadFn(void *); + void threadFn(); public: ServerConnection(CActiveSocket *socket); diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index b8ccb67db..ea0937784 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -116,6 +116,11 @@ namespace DFHack DFHACK_EXPORT void describeName(NameInfo *info, df::language_name *name); + using dfproto::NameTriple; + + DFHACK_EXPORT void describeNameTriple(NameTriple *info, const std::string &name, + const std::string &plural, const std::string &adj); + using dfproto::BasicUnitInfo; using dfproto::BasicUnitInfoMask; diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 6df8b2c19..0fd75bf1f 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -194,6 +194,10 @@ DFHACK_EXPORT bool RemoveOwnedItemByIdx(const uint32_t index, int32_t id); DFHACK_EXPORT bool RemoveOwnedItemByPtr(df::unit * unit, int32_t id); DFHACK_EXPORT df::language_name *GetVisibleName(df::unit *unit); + +DFHACK_EXPORT bool isDead(df::unit *unit); +DFHACK_EXPORT bool isAlive(df::unit *unit); +DFHACK_EXPORT bool isSane(df::unit *unit); } } #endif diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 9e97f8e74..f6259bd3b 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -54,6 +54,7 @@ using namespace std; #include "df/assumed_identity.h" using namespace DFHack; +using namespace df::enums; using df::global::world; using df::global::ui; @@ -551,3 +552,39 @@ df::language_name *Units::GetVisibleName(df::unit *unit) return &unit->name; } + +bool DFHack::Units::isDead(df::unit *unit) +{ + return unit->flags1.bits.dead; +} + +bool DFHack::Units::isAlive(df::unit *unit) +{ + return !unit->flags1.bits.dead && + !unit->flags3.bits.ghostly && + !unit->curse.add_tags1.bits.NOT_LIVING; +} + +bool DFHack::Units::isSane(df::unit *unit) +{ + if (unit->flags1.bits.dead || + unit->flags3.bits.ghostly || + unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || + unit->curse.add_tags1.bits.CRAZED) + return false; + + if (unit->flags1.bits.has_mood) + { + switch (unit->mood) + { + case mood_type::Melancholy: + case mood_type::Raving: + case mood_type::Berserk: + return false; + default: + break; + } + } + + return true; +} diff --git a/library/proto/Basic.proto b/library/proto/Basic.proto index 6e58345ff..ead41cd81 100644 --- a/library/proto/Basic.proto +++ b/library/proto/Basic.proto @@ -5,6 +5,8 @@ option optimize_for = LITE_RUNTIME; message EnumItemName { required int32 value = 1; optional string name = 2; + + // For bitfield members optional int32 bit_size = 3 [default = 1]; }; @@ -16,10 +18,15 @@ message BasicMaterialId { message BasicMaterialInfo { required int32 type = 1; required sint32 index = 2; + + // The raw token required string token = 3; - repeated int32 flags = 4; // of material_flags + // IF mask.flags: + // List of material_flags indices + repeated int32 flags = 4; + // Material type/index expanded: optional int32 subtype = 5 [default = -1]; optional int32 creature_id = 6 [default = -1]; optional int32 plant_id = 7 [default = -1]; @@ -27,10 +34,13 @@ message BasicMaterialInfo { optional string name_prefix = 9 [default = ""]; + // IF mask.states: in listed order; + // ELSE: one state matching mask.temperature repeated fixed32 state_color = 10; repeated string state_name = 11; repeated string state_adj = 12; + // IF mask.reaction: message Product { required string id = 1; required int32 type = 2; @@ -39,6 +49,7 @@ message BasicMaterialInfo { repeated string reaction_class = 13; repeated Product reaction_product = 14; + // IF mask.flags: repeated int32 inorganic_flags = 15; }; @@ -68,6 +79,21 @@ message NameInfo { optional string english_name = 5; }; +message NameTriple { + required string normal = 1; + optional string plural = 2; + optional string adjective = 3; +}; + +message UnitCurseInfo { + required fixed32 add_tags1 = 1; + required fixed32 rem_tags1 = 2; + required fixed32 add_tags2 = 3; + required fixed32 rem_tags2 = 4; + + optional NameTriple name = 5; +}; + message SkillInfo { required int32 id = 1; required int32 level = 2; @@ -77,6 +103,10 @@ message SkillInfo { message BasicUnitInfo { required int32 unit_id = 1; + required int32 pos_x = 13; + required int32 pos_y = 14; + required int32 pos_z = 15; + optional NameInfo name = 2; required fixed32 flags1 = 3; @@ -90,25 +120,41 @@ message BasicUnitInfo { optional int32 civ_id = 9 [default = -1]; optional int32 histfig_id = 10 [default = -1]; + optional int32 death_id = 17 [default = -1]; + optional uint32 death_flags = 18; + + // IF mask.profession: + optional int32 squad_id = 19 [default = -1]; + optional int32 squad_position = 20 [default = -1]; + + optional int32 profession = 22 [default = -1]; + optional string custom_profession = 23; + + // IF mask.labors: repeated int32 labors = 11; + // IF mask.skills: repeated SkillInfo skills = 12; - required int32 pos_x = 13; - required int32 pos_y = 14; - required int32 pos_z = 15; + optional UnitCurseInfo curse = 16; + + repeated int32 burrows = 21; }; message BasicUnitInfoMask { optional bool labors = 1 [default = false]; optional bool skills = 2 [default = false]; + optional bool profession = 3 [default = false]; }; message BasicSquadInfo { required int32 squad_id = 1; optional NameInfo name = 2; + + // A special field completely overriding the name: optional string alias = 3; + // Member histfig ids: repeated sint32 members = 4; }; diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto index 33f2a5764..7b105ee88 100644 --- a/library/proto/BasicApi.proto +++ b/library/proto/BasicApi.proto @@ -4,6 +4,29 @@ option optimize_for = LITE_RUNTIME; import "Basic.proto"; +message GetWorldInfoOut { + enum Mode { + MODE_DWARF = 1; + MODE_ADVENTURE = 2; + MODE_LEGENDS = 3; + }; + required Mode mode = 1; + + required string save_dir = 2; + optional NameInfo world_name = 3; + + // Dwarf mode + optional int32 civ_id = 4; + optional int32 site_id = 5; + optional int32 group_id = 6; + optional int32 race_id = 7; + + // Adventure mode + optional int32 player_unit_id = 8; + optional int32 player_histfig_id = 9; + repeated int32 companion_histfig_ids = 10; +}; + message ListEnumsOut { repeated EnumItemName material_flags = 1; repeated EnumItemName inorganic_flags = 2; @@ -14,11 +37,22 @@ message ListEnumsOut { repeated EnumItemName unit_labor = 6; repeated EnumItemName job_skill = 7; + + repeated EnumItemName cie_add_tag_mask1 = 8; + repeated EnumItemName cie_add_tag_mask2 = 9; + + repeated EnumItemName death_info_flags = 10; + + repeated EnumItemName profession = 11; }; message ListMaterialsIn { optional BasicMaterialInfoMask mask = 1; + + // Specific materials: repeated BasicMaterialId id_list = 2; + + // Complete list by type: optional bool builtin = 3; optional bool inorganic = 4; optional bool creatures = 5; @@ -30,10 +64,18 @@ message ListMaterialsOut { message ListUnitsIn { optional BasicUnitInfoMask mask = 1; + + // Specific units: repeated int32 id_list = 2; + // All units matching: + optional bool scan_all = 5; + optional int32 race = 3; optional int32 civ_id = 4; + optional bool dead = 6; // i.e. passive corpse + optional bool alive = 7; // i.e. not dead or undead + optional bool sane = 8; // not dead, ghost, zombie, or insane }; message ListUnitsOut { repeated BasicUnitInfo value = 1; diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 9544901fc..2384fbb3b 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -124,8 +124,6 @@ void setUnitNickname(df::unit *unit, const std::string &nick) static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in) { - CoreSuspender suspend; - df::squad *squad = df::squad::find(in->squad_id()); if (!squad) return CR_NOT_FOUND; @@ -140,8 +138,6 @@ static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) { - CoreSuspender suspend; - df::unit *unit = df::unit::find(in->unit_id()); if (!unit) return CR_NOT_FOUND;