From ce3ee386fdc48e39d624014e7d87b9cca71b2b83 Mon Sep 17 00:00:00 2001 From: 20k Date: Sat, 21 Jan 2023 20:07:12 +0000 Subject: [PATCH] makeSquad, updateRoomAssignments --- docs/dev/Lua API.rst | 13 ++ library/LuaApi.cpp | 2 + library/include/modules/Units.h | 3 + library/modules/Units.cpp | 278 ++++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+) diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index c2cc7f5cd..b089fb8ea 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -1589,6 +1589,19 @@ Units module Returns a table of the cutoffs used by the above stress level functions. +* ``dfhack.units.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)`` + + Sets the sleep, train, indiv_eq, and squad_eq flags when training at a barracks + +* ``dfhack.units.getSquadName(squad)`` + + Returns the name of a squad + Action Timer API ~~~~~~~~~~~~~~~~ diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 426c87566..af56bb4c3 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1812,6 +1812,8 @@ 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), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 0edebacdc..f3c7baf6a 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -37,6 +37,7 @@ 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,6 +224,8 @@ 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/Units.cpp b/library/modules/Units.cpp index 9a415fb34..0ab507517 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -33,6 +33,7 @@ distribution. #include #include #include +#include using namespace std; #include "VersionInfo.h" @@ -51,6 +52,7 @@ using namespace std; #include "MiscUtils.h" #include "df/activity_entry.h" +#include "df/building_civzonest.h" #include "df/burrow.h" #include "df/caste_raw.h" #include "df/creature_raw.h" @@ -61,6 +63,7 @@ using namespace std; #include "df/entity_raw_flags.h" #include "df/identity_type.h" #include "df/game_mode.h" +#include "df/global_objects.h" #include "df/histfig_entity_link_positionst.h" #include "df/histfig_relationship_type.h" #include "df/historical_entity.h" @@ -72,6 +75,10 @@ using namespace std; #include "df/job.h" #include "df/nemesis_record.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/tile_occupancy.h" #include "df/plotinfost.h" #include "df/unit_inventory_item.h" @@ -1972,6 +1979,277 @@ std::string Units::getSquadName(df::unit *unit) 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);