From 3912c6290f632d3cb1f41620aeeae1a0e050eaf3 Mon Sep 17 00:00:00 2001 From: 20k Date: Mon, 30 Jan 2023 06:28:23 +0000 Subject: [PATCH] Military module start --- docs/dev/Lua API.rst | 9 +- library/CMakeLists.txt | 2 + library/LuaApi.cpp | 14 +- library/include/modules/Military.h | 19 ++ library/include/modules/Units.h | 5 - library/modules/Military.cpp | 307 +++++++++++++++++++++++++++++ library/modules/Units.cpp | 284 -------------------------- 7 files changed, 345 insertions(+), 295 deletions(-) create mode 100644 library/include/modules/Military.h create mode 100644 library/modules/Military.cpp diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index b089fb8ea..9125ca902 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -1589,16 +1589,19 @@ Units module Returns a table of the cutoffs used by the above stress level functions. -* ``dfhack.units.makeSquad(assignment_id)`` +Military Module API +~~~~~~~~~~~~~~~~~~~ + +* ``dfhack.military.makeSquad(assignment_id)`` Creates a new squad associated with the assignment. Fails if one already exists Note: This function does not name the squad, but they are otherwise complete -* ``dfhack.units.updateRoomAssignments(squad_id, assignment_id, squad_use_flags)`` +* ``dfhack.military.updateRoomAssignments(squad_id, assignment_id, squad_use_flags)`` Sets the sleep, train, indiv_eq, and squad_eq flags when training at a barracks -* ``dfhack.units.getSquadName(squad)`` +* ``dfhack.military.getSquadName(squad)`` Returns the name of a squad diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 87f869493..92db43563 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -137,6 +137,7 @@ set(MODULE_HEADERS include/modules/MapCache.h include/modules/Maps.h include/modules/Materials.h + include/modules/Military.h include/modules/Once.h include/modules/Persistence.h include/modules/Random.h @@ -164,6 +165,7 @@ set(MODULE_SOURCES modules/MapCache.cpp modules/Maps.cpp modules/Materials.cpp + modules/Military.cpp modules/Once.cpp modules/Persistence.cpp modules/Random.cpp diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index af56bb4c3..13ea1a7fe 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -55,6 +55,7 @@ distribution. #include "modules/MapCache.h" #include "modules/Maps.h" #include "modules/Materials.h" +#include "modules/Military.h" #include "modules/Random.h" #include "modules/Screen.h" #include "modules/Textures.h" @@ -1812,9 +1813,6 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getGoalType), WRAPM(Units, getGoalName), WRAPM(Units, isGoalAchieved), - WRAPM(Units, makeSquad), - WRAPM(Units, updateRoomAssignments), - WRAPM(Units, getSquadName), WRAPM(Units, getPhysicalDescription), WRAPM(Units, getRaceName), WRAPM(Units, getRaceNamePlural), @@ -1939,6 +1937,15 @@ static const luaL_Reg dfhack_units_funcs[] = { { NULL, NULL } }; +/***** Military Module *****/ + +static const LuaWrapper::FunctionReg dfhack_military_module[] = { + WRAPM(Military, makeSquad), + WRAPM(Military, updateRoomAssignments), + WRAPM(Military, getSquadName), + { NULL, NULL } +}; + /***** Items module *****/ static bool items_moveToGround(df::item *item, df::coord pos) @@ -3449,6 +3456,7 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "textures", dfhack_textures_module); OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); + OpenModule(state, "military", dfhack_military_module); OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs); OpenModule(state, "world", dfhack_world_module, dfhack_world_funcs); diff --git a/library/include/modules/Military.h b/library/include/modules/Military.h new file mode 100644 index 000000000..19ed47ee2 --- /dev/null +++ b/library/include/modules/Military.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Export.h" +#include "DataDefs.h" + +#include "df/squad.h" +#include "df/unit.h" + +namespace DFHack +{ +namespace Military +{ + +DFHACK_EXPORT std::string getSquadName(df::unit *unit); +DFHACK_EXPORT df::squad* makeSquad(int32_t assignment_id); +DFHACK_EXPORT void updateRoomAssignments(int32_t squad_id, int32_t civzone_id, df::squad_use_flags flags); + +} +} \ No newline at end of file diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index f3c7baf6a..4fd9246aa 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -37,7 +37,6 @@ distribution. #include "df/mental_attribute_type.h" #include "df/misc_trait_type.h" #include "df/physical_attribute_type.h" -#include "df/squad.h" #include "df/unit.h" #include "df/unit_action.h" #include "df/unit_action_type_group.h" @@ -223,10 +222,6 @@ DFHACK_EXPORT df::goal_type getGoalType(df::unit *unit, size_t goalIndex = 0); DFHACK_EXPORT std::string getGoalName(df::unit *unit, size_t goalIndex = 0); DFHACK_EXPORT bool isGoalAchieved(df::unit *unit, size_t goalIndex = 0); -DFHACK_EXPORT std::string getSquadName(df::unit *unit); -DFHACK_EXPORT df::squad* makeSquad(int32_t assignment_id); -DFHACK_EXPORT void updateRoomAssignments(int32_t squad_id, int32_t civzone_id, df::squad_use_flags flags); - DFHACK_EXPORT df::activity_entry *getMainSocialActivity(df::unit *unit); DFHACK_EXPORT df::activity_event *getMainSocialEvent(df::unit *unit); diff --git a/library/modules/Military.cpp b/library/modules/Military.cpp new file mode 100644 index 000000000..b67b3a129 --- /dev/null +++ b/library/modules/Military.cpp @@ -0,0 +1,307 @@ +#include +#include +#include +#include "modules/Military.h" +#include "modules/Translation.h" +#include "df/building.h" +#include "df/building_civzonest.h" +#include "df/historical_figure.h" +#include "df/historical_entity.h" +#include "df/entity_position.h" +#include "df/entity_position_assignment.h" +#include "df/plotinfost.h" +#include "df/squad.h" +#include "df/squad_position.h" +#include "df/squad_schedule_order.h" +#include "df/squad_order.h" +#include "df/squad_order_trainst.h" +#include "df/world.h" + +using namespace DFHack; +using namespace df::enums; +using df::global::world; +using df::global::plotinfo; + +std::string Military::getSquadName(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->military.squad_id == -1) + return ""; + df::squad *squad = df::squad::find(unit->military.squad_id); + if (!squad) + return ""; + if (squad->alias.size() > 0) + return squad->alias; + return Translation::TranslateName(&squad->name, true); +} + +//only works for making squads for fort mode player controlled dwarf squads +//could be extended straightforwardly by passing in entity +df::squad* Military::makeSquad(int32_t assignment_id) +{ + if (df::global::squad_next_id == nullptr || df::global::plotinfo == nullptr) + return nullptr; + + df::language_name name; + name.type = df::language_name_type::Squad; + + for (int i=0; i < 7; i++) + { + name.words[i] = -1; + name.parts_of_speech[i] = df::part_of_speech::Noun; + } + + df::historical_entity* fort = df::historical_entity::find(df::global::plotinfo->group_id); + + df::entity_position_assignment* found_assignment = nullptr; + + for (auto* assignment : fort->positions.assignments) + { + if (assignment->id == assignment_id) + { + found_assignment = assignment; + break; + } + } + + if (found_assignment == nullptr) + return nullptr; + + //this function does not attempt to delete or replace squads for assignments + if (found_assignment->squad_id != -1) + return nullptr; + + df::entity_position* corresponding_position = nullptr; + + for (auto* position : fort->positions.own) + { + if (position->id == found_assignment->position_id) + { + corresponding_position = position; + break; + } + } + + if (corresponding_position == nullptr) + return nullptr; + + df::squad* result = new df::squad(); + result->id = *df::global::squad_next_id; + result->cur_routine_idx = 0; + result->uniform_priority = result->id + 1; //no idea why, but seems to hold + result->activity = -1; //?? + result->carry_food = 2; + result->carry_water = 1; + result->entity_id = df::global::plotinfo->group_id; + result->leader_position = corresponding_position->id; + result->leader_assignment = found_assignment->id; + result->unk_1 = -1; + result->name = name; + result->ammo.unk_v50_1 = 0; + + int16_t squad_size = corresponding_position->squad_size; + + for (int i=0; i < squad_size; i++) + { + //construct for squad_position seems to set all the attributes correctly + //except I've observed unk_2 is -1 generally + df::squad_position* pos = new df::squad_position(); + pos->unk_2 = -1; + pos->flags.whole = 0; + + result->positions.push_back(pos); + } + + const auto& routines = df::global::plotinfo->alerts.routines; + + for (const auto& routine : routines) + { + df::squad_schedule_entry* asched = (df::squad_schedule_entry*)malloc(sizeof(df::squad_schedule_entry) * 12); + + for(int kk=0; kk < 12; kk++) + { + new (&asched[kk]) df::squad_schedule_entry; + + for(int jj=0; jj < squad_size; jj++) + { + int32_t* order_assignments = new int32_t(); + *order_assignments = -1; + + asched[kk].order_assignments.push_back(order_assignments); + } + } + + auto insert_training_order = [asched, squad_size](int month) + { + df::squad_schedule_order* order = new df::squad_schedule_order(); + order->min_count = squad_size; + //assumed + order->positions.resize(squad_size); + + df::squad_order* s_order = df::allocate(); + + s_order->unk_v40_1 = -1; + s_order->unk_v40_2 = -1; + s_order->year = *df::global::cur_year; + s_order->year_tick = *df::global::cur_year_tick; + s_order->unk_v40_3 = -1; + s_order->unk_1 = 0; + + order->order = s_order; + + asched[month].orders.push_back(order); + //wear uniform while training + asched[month].uniform_mode = 0; + }; + + //I thought this was a terrible hack, but its literally how dwarf fortress does it 1:1 + //Off duty: No orders, Sleep/room at will. Equip/orders only + if (routine->name == "Off duty") + { + for (int i=0; i < 12; i++) + { + asched[i].sleep_mode = 0; + asched[i].uniform_mode = 1; + } + } + //Staggered Training: Training orders at 3 4 5, 9 10 11, sleep/room at will. Equip/orders only, except train months which are equip/always + //always seen the training indices 0 1 2 6 7 8, so its unclear. Check if squad id matters + else if (routine->name == "Staggered training") + { + //this is semi randomised for different squads + //appears to be something like squad.id & 1, it isn't smart + //if you alternate squad creation, its 'correctly' staggered + //but it'll also happily not stagger them if you eg delete a squad and make another + std::array indices; + + if ((*df::global::squad_next_id) & 1) + { + indices = {3, 4, 5, 9, 10, 11}; + } + else + { + indices = {0, 1, 2, 6, 7, 8}; + } + + for (int index : indices) + { + insert_training_order(index); + //still sleep in room at will even when training + asched[index].sleep_mode = 0; + } + } + //see above, but with all indices + else if (routine->name == "Constant training") + { + for (int i=0; i < 12; i++) + { + insert_training_order(i); + //still sleep in room at will even when training + asched[i].sleep_mode = 0; + } + } + else if (routine->name == "Ready") + { + for (int i=0; i < 12; i++) + { + asched[i].sleep_mode = 2; + asched[i].uniform_mode = 0; + } + } + else + { + for (int i=0; i < 12; i++) + { + asched[i].sleep_mode = 0; + asched[i].uniform_mode = 0; + } + } + + result->schedule.push_back(reinterpret_cast(asched)); + } + + //all we've done so far is leak memory if anything goes wrong + //modify state + (*df::global::squad_next_id)++; + fort->squads.push_back(result->id); + df::global::world->squads.all.push_back(result); + found_assignment->squad_id = result->id; + + //todo: find and modify old squad + + return result; +} + +void Military::updateRoomAssignments(int32_t squad_id, int32_t civzone_id, df::squad_use_flags flags) +{ + df::squad* squad = df::squad::find(squad_id); + df::building* bzone = df::building::find(civzone_id); + + df::building_civzonest* zone = strict_virtual_cast(bzone); + + if (squad == nullptr || zone == nullptr) + return; + + df::squad::T_rooms* room_from_squad = nullptr; + df::building_civzonest::T_squad_room_info* room_from_building = nullptr; + + for (auto room : squad->rooms) + { + if (room->building_id == civzone_id) + { + room_from_squad = room; + break; + } + } + + for (auto room : zone->squad_room_info) + { + if (room->squad_id == squad_id) + { + room_from_building = room; + break; + } + } + + if (flags.whole == 0 && room_from_squad == nullptr && room_from_building == nullptr) + return; + + //if we're setting 0 flags, and there's no room already, don't set a room + bool avoiding_squad_roundtrip = flags.whole == 0 && room_from_squad == nullptr; + + if (!avoiding_squad_roundtrip && room_from_squad == nullptr) + { + room_from_squad = new df::squad::T_rooms(); + room_from_squad->building_id = civzone_id; + squad->rooms.push_back(room_from_squad); + + std::sort(squad->rooms.begin(), squad->rooms.end(), [](df::squad::T_rooms* a, df::squad::T_rooms* b){return a->building_id < b->building_id;}); + } + + if (room_from_building == nullptr) + { + room_from_building = new df::building_civzonest::T_squad_room_info(); + room_from_building->squad_id = squad_id; + zone->squad_room_info.push_back(room_from_building); + + std::sort(zone->squad_room_info.begin(), zone->squad_room_info.end(), [](df::building_civzonest::T_squad_room_info* a, df::building_civzonest::T_squad_room_info* b){return a->squad_id < b->squad_id;}); + } + + if (room_from_squad) + room_from_squad->mode = flags; + + room_from_building->mode = flags; + + if (flags.whole == 0 && !avoiding_squad_roundtrip) + { + for (int i=0; i < (int)squad->rooms.size(); i++) + { + if (squad->rooms[i]->building_id == civzone_id) + { + delete squad->rooms[i]; + squad->rooms.erase(squad->rooms.begin() + i); + i--; + } + } + } +} \ No newline at end of file diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 0ab507517..137a07da6 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -1966,290 +1966,6 @@ bool Units::isGoalAchieved(df::unit *unit, size_t goalIndex) && unit->status.current_soul->personality.dreams[goalIndex]->flags.whole != 0; } -std::string Units::getSquadName(df::unit *unit) -{ - CHECK_NULL_POINTER(unit); - if (unit->military.squad_id == -1) - return ""; - df::squad *squad = df::squad::find(unit->military.squad_id); - if (!squad) - return ""; - if (squad->alias.size() > 0) - return squad->alias; - return Translation::TranslateName(&squad->name, true); -} - -//only works for making squads for fort mode player controlled dwarf squads -//could be extended straightforwardly by passing in entity -df::squad* Units::makeSquad(int32_t assignment_id) -{ - if (df::global::squad_next_id == nullptr || df::global::plotinfo == nullptr) - return nullptr; - - df::language_name name; - name.type = df::language_name_type::Squad; - - for (int i=0; i < 7; i++) - { - name.words[i] = -1; - name.parts_of_speech[i] = df::part_of_speech::Noun; - } - - df::historical_entity* fort = df::historical_entity::find(df::global::plotinfo->group_id); - - df::entity_position_assignment* found_assignment = nullptr; - - for (auto* assignment : fort->positions.assignments) - { - if (assignment->id == assignment_id) - { - found_assignment = assignment; - break; - } - } - - if (found_assignment == nullptr) - return nullptr; - - //this function does not attempt to delete or replace squads for assignments - if (found_assignment->squad_id != -1) - return nullptr; - - df::entity_position* corresponding_position = nullptr; - - for (auto* position : fort->positions.own) - { - if (position->id == found_assignment->position_id) - { - corresponding_position = position; - break; - } - } - - if (corresponding_position == nullptr) - return nullptr; - - df::squad* result = new df::squad(); - result->id = *df::global::squad_next_id; - result->cur_routine_idx = 0; - result->uniform_priority = result->id + 1; //no idea why, but seems to hold - result->activity = -1; //?? - result->carry_food = 2; - result->carry_water = 1; - result->entity_id = df::global::plotinfo->group_id; - result->leader_position = corresponding_position->id; - result->leader_assignment = found_assignment->id; - result->unk_1 = -1; - result->name = name; - result->ammo.unk_v50_1 = 0; - - int16_t squad_size = corresponding_position->squad_size; - - for (int i=0; i < squad_size; i++) - { - //construct for squad_position seems to set all the attributes correctly - //except I've observed unk_2 is -1 generally - df::squad_position* pos = new df::squad_position(); - pos->unk_2 = -1; - pos->flags.whole = 0; - - result->positions.push_back(pos); - } - - const auto& routines = df::global::plotinfo->alerts.routines; - - for (const auto& routine : routines) - { - df::squad_schedule_entry* asched = (df::squad_schedule_entry*)malloc(sizeof(df::squad_schedule_entry) * 12); - - for(int kk=0; kk < 12; kk++) - { - new (&asched[kk]) df::squad_schedule_entry; - - for(int jj=0; jj < squad_size; jj++) - { - int32_t* order_assignments = new int32_t(); - *order_assignments = -1; - - asched[kk].order_assignments.push_back(order_assignments); - } - } - - auto insert_training_order = [asched, squad_size](int month) - { - df::squad_schedule_order* order = new df::squad_schedule_order(); - order->min_count = squad_size; - //assumed - order->positions.resize(squad_size); - - df::squad_order* s_order = df::allocate(); - - s_order->unk_v40_1 = -1; - s_order->unk_v40_2 = -1; - s_order->year = *df::global::cur_year; - s_order->year_tick = *df::global::cur_year_tick; - s_order->unk_v40_3 = -1; - s_order->unk_1 = 0; - - order->order = s_order; - - asched[month].orders.push_back(order); - //wear uniform while training - asched[month].uniform_mode = 0; - }; - - //I thought this was a terrible hack, but its literally how dwarf fortress does it 1:1 - //Off duty: No orders, Sleep/room at will. Equip/orders only - if (routine->name == "Off duty") - { - for (int i=0; i < 12; i++) - { - asched[i].sleep_mode = 0; - asched[i].uniform_mode = 1; - } - } - //Staggered Training: Training orders at 3 4 5, 9 10 11, sleep/room at will. Equip/orders only, except train months which are equip/always - //always seen the training indices 0 1 2 6 7 8, so its unclear. Check if squad id matters - else if (routine->name == "Staggered training") - { - //this is semi randomised for different squads - //appears to be something like squad.id & 1, it isn't smart - //if you alternate squad creation, its 'correctly' staggered - //but it'll also happily not stagger them if you eg delete a squad and make another - std::array indices; - - if ((*df::global::squad_next_id) & 1) - { - indices = {3, 4, 5, 9, 10, 11}; - } - else - { - indices = {0, 1, 2, 6, 7, 8}; - } - - for (int index : indices) - { - insert_training_order(index); - //still sleep in room at will even when training - asched[index].sleep_mode = 0; - } - } - //see above, but with all indices - else if (routine->name == "Constant training") - { - for (int i=0; i < 12; i++) - { - insert_training_order(i); - //still sleep in room at will even when training - asched[i].sleep_mode = 0; - } - } - else if (routine->name == "Ready") - { - for (int i=0; i < 12; i++) - { - asched[i].sleep_mode = 2; - asched[i].uniform_mode = 0; - } - } - else - { - for (int i=0; i < 12; i++) - { - asched[i].sleep_mode = 0; - asched[i].uniform_mode = 0; - } - } - - result->schedule.push_back(reinterpret_cast(asched)); - } - - //all we've done so far is leak memory if anything goes wrong - //modify state - (*df::global::squad_next_id)++; - fort->squads.push_back(result->id); - df::global::world->squads.all.push_back(result); - found_assignment->squad_id = result->id; - - //todo: find and modify old squad - - return result; -} - -void Units::updateRoomAssignments(int32_t squad_id, int32_t civzone_id, df::squad_use_flags flags) -{ - df::squad* squad = df::squad::find(squad_id); - df::building* bzone = df::building::find(civzone_id); - - df::building_civzonest* zone = strict_virtual_cast(bzone); - - if (squad == nullptr || zone == nullptr) - return; - - df::squad::T_rooms* room_from_squad = nullptr; - df::building_civzonest::T_squad_room_info* room_from_building = nullptr; - - for (auto room : squad->rooms) - { - if (room->building_id == civzone_id) - { - room_from_squad = room; - break; - } - } - - for (auto room : zone->squad_room_info) - { - if (room->squad_id == squad_id) - { - room_from_building = room; - break; - } - } - - if (flags.whole == 0 && room_from_squad == nullptr && room_from_building == nullptr) - return; - - //if we're setting 0 flags, and there's no room already, don't set a room - bool avoiding_squad_roundtrip = flags.whole == 0 && room_from_squad == nullptr; - - if (!avoiding_squad_roundtrip && room_from_squad == nullptr) - { - room_from_squad = new df::squad::T_rooms(); - room_from_squad->building_id = civzone_id; - squad->rooms.push_back(room_from_squad); - - std::sort(squad->rooms.begin(), squad->rooms.end(), [](df::squad::T_rooms* a, df::squad::T_rooms* b){return a->building_id < b->building_id;}); - } - - if (room_from_building == nullptr) - { - room_from_building = new df::building_civzonest::T_squad_room_info(); - room_from_building->squad_id = squad_id; - zone->squad_room_info.push_back(room_from_building); - - std::sort(zone->squad_room_info.begin(), zone->squad_room_info.end(), [](df::building_civzonest::T_squad_room_info* a, df::building_civzonest::T_squad_room_info* b){return a->squad_id < b->squad_id;}); - } - - if (room_from_squad) - room_from_squad->mode = flags; - - room_from_building->mode = flags; - - if (flags.whole == 0 && !avoiding_squad_roundtrip) - { - for (int i=0; i < (int)squad->rooms.size(); i++) - { - if (squad->rooms[i]->building_id == civzone_id) - { - delete squad->rooms[i]; - squad->rooms.erase(squad->rooms.begin() + i); - i--; - } - } - } -} - df::activity_entry *Units::getMainSocialActivity(df::unit *unit) { CHECK_NULL_POINTER(unit);