Military module start

develop
20k 2023-01-30 06:28:23 +00:00
parent ce3ee386fd
commit 3912c6290f
7 changed files with 345 additions and 295 deletions

@ -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

@ -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

@ -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);

@ -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);
}
}

@ -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);

@ -0,0 +1,307 @@
#include <string>
#include <vector>
#include <array>
#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<df::squad_order_trainst>();
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<int, 6> 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<df::squad::T_schedule*>(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<df::building_civzonest>(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--;
}
}
}
}

@ -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<df::squad_order_trainst>();
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<int, 6> 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<df::squad::T_schedule*>(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<df::building_civzonest>(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);