Merge branch 'siege-reports' into develop

develop
Alexander Gavrilov 2014-04-21 09:05:12 +04:00
commit 4b8760815d
14 changed files with 573 additions and 79 deletions

@ -1160,6 +1160,26 @@ the container itself.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getSelectedBuilding([silent])</span></tt></p>
<p>Returns the building selected via <em>'q'</em>, <em>'t'</em>, <em>'k'</em> or <em>'i'</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.gui.writeToGamelog(text)</tt></p>
<p>Writes a string to gamelog.txt without doing an announcement.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.makeAnnouncement(type,flags,pos,text,color[,is_bright])</span></tt></p>
<p>Adds an announcement with given announcement_type, text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.</p>
<p>The announcement is written to gamelog.txt. The announcement_flags
argument provides a custom set of announcements.txt options,
which specify if the message should actually be displayed in the
announcement list, and whether to recenter or show a popup.</p>
<p>Returns the index of the new announcement in <tt class="docutils literal">df.global.world.status.reports</tt>, or -1.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.gui.addCombatReport(unit,slot,report_index)</tt></p>
<p>Adds the report with the given index (returned by makeAnnouncement)
to the specified group of the given unit. Returns <em>true</em> on success.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.gui.addCombatReportAuto(unit,flags,report_index)</tt></p>
<p>Adds the report with the given index to the appropriate group(s)
of the given unit, as requested by the flags.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAnnouncement(text,color[,is_bright])</span></tt></p>
<p>Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.</p>
@ -1170,9 +1190,9 @@ The is_bright boolean actually seems to invert the brightness.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showPopupAnnouncement(text,color[,is_bright])</span></tt></p>
<p>Pops up a titan-style modal announcement window.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])</span></tt></p>
<p>Uses the type to look up options from announcements.txt, and calls the
above operations accordingly. If enabled, pauses and zooms to position.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright,unit1,unit2])</span></tt></p>
<p>Uses the type to look up options from announcements.txt, and calls the above
operations accordingly. The units are used to call <tt class="docutils literal">addCombatReportAuto</tt>.</p>
</li>
</ul>
</div>
@ -1200,6 +1220,14 @@ above operations accordingly. If enabled, pauses and zooms to position.</p>
<li><p class="first"><tt class="docutils literal">dfhack.job.getWorker(job)</tt></p>
<p>Returns the unit performing the job.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.setJobCooldown(building,worker,timeout)</tt></p>
<p>Prevent the worker from taking jobs at the specified workshop for the specified time.
This doesn't decrease the timeout in any circumstances.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.removeWorker(job,timeout)</tt></p>
<p>Removes the worker from the specified workshop job, and sets the cooldown.
Returns <em>true</em> on success.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.checkBuildingsNow()</tt></p>
<p>Instructs the game to check buildings for jobs next frame and assign workers.</p>
</li>
@ -1306,6 +1334,10 @@ is <em>true</em>, subtracts the rust penalty.</p>
<li><p class="first"><tt class="docutils literal">dfhack.units.computeMovementSpeed(unit)</tt></p>
<p>Computes number of frames * 100 it takes the unit to move in its current state of mind and body.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.computeSlowdownFactor(unit)</tt></p>
<p>Meandering and floundering in liquid introduces additional slowdown. It is
random, but the function computes and returns the expected mean factor as a float.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNoblePositions(unit)</tt></p>
<p>Returns a list of tables describing noble position assignments, or <em>nil</em>.
Every table has fields <tt class="docutils literal">entity</tt>, <tt class="docutils literal">assignment</tt> and <tt class="docutils literal">position</tt>.</p>

@ -862,6 +862,32 @@ Gui module
Returns the building selected via *'q'*, *'t'*, *'k'* or *'i'*.
* ``dfhack.gui.writeToGamelog(text)``
Writes a string to gamelog.txt without doing an announcement.
* ``dfhack.gui.makeAnnouncement(type,flags,pos,text,color[,is_bright])``
Adds an announcement with given announcement_type, text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.
The announcement is written to gamelog.txt. The announcement_flags
argument provides a custom set of announcements.txt options,
which specify if the message should actually be displayed in the
announcement list, and whether to recenter or show a popup.
Returns the index of the new announcement in ``df.global.world.status.reports``, or -1.
* ``dfhack.gui.addCombatReport(unit,slot,report_index)``
Adds the report with the given index (returned by makeAnnouncement)
to the specified group of the given unit. Returns *true* on success.
* ``dfhack.gui.addCombatReportAuto(unit,flags,report_index)``
Adds the report with the given index to the appropriate group(s)
of the given unit, as requested by the flags.
* ``dfhack.gui.showAnnouncement(text,color[,is_bright])``
Adds a regular announcement with given text, color, and brightness.
@ -875,10 +901,10 @@ Gui module
Pops up a titan-style modal announcement window.
* ``dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])``
* ``dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright,unit1,unit2])``
Uses the type to look up options from announcements.txt, and calls the
above operations accordingly. If enabled, pauses and zooms to position.
Uses the type to look up options from announcements.txt, and calls the above
operations accordingly. The units are used to call ``addCombatReportAuto``.
Job module
@ -912,6 +938,16 @@ Job module
Returns the unit performing the job.
* ``dfhack.job.setJobCooldown(building,worker,timeout)``
Prevent the worker from taking jobs at the specified workshop for the specified time.
This doesn't decrease the timeout in any circumstances.
* ``dfhack.job.removeWorker(job,timeout)``
Removes the worker from the specified workshop job, and sets the cooldown.
Returns *true* on success.
* ``dfhack.job.checkBuildingsNow()``
Instructs the game to check buildings for jobs next frame and assign workers.
@ -1041,6 +1077,11 @@ Units module
Computes number of frames * 100 it takes the unit to move in its current state of mind and body.
* ``dfhack.units.computeSlowdownFactor(unit)``
Meandering and floundering in liquid introduces additional slowdown. It is
random, but the function computes and returns the expected mean factor as a float.
* ``dfhack.units.getNoblePositions(unit)``
Returns a list of tables describing noble position assignments, or *nil*.

@ -3,6 +3,7 @@ DFHack future
Internals:
- support for calling a lua function via a protobuf request (demonstrated by dfhack-run --lua).
- Lua API for listing files in directory. Needed for mod-manager.
- Lua API for creating unit combat reports and writing to gamelog.
- support for multiple raw/init.d/*.lua init scripts in one save.
New scripts:
@ -17,9 +18,15 @@ DFHack future
Misc improvements:
- digfort: improved csv parsing, add start() comment handling
- exterminate: allow specifying a caste (exterminate gob:male)
- siege-engine: engine quality and distance to target now affect accuracy.
- createitem: in adventure mode it now defaults to the controlled unit as maker.
Siege engine plugin:
- engine quality and distance to target now affect accuracy
- firing the siege engine at a target produces a combat report
- improved movement speed computation for meandering units
- operators in Prepare To Fire mode are released from duty once
hungry/thirsty if there is a free replacement
DFHack v0.34.11-r4
New commands:

@ -1307,6 +1307,10 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem),
WRAPM(Gui, getSelectedBuilding),
WRAPM(Gui, writeToGamelog),
WRAPM(Gui, makeAnnouncement),
WRAPM(Gui, addCombatReport),
WRAPM(Gui, addCombatReportAuto),
WRAPM(Gui, showAnnouncement),
WRAPM(Gui, showZoomAnnouncement),
WRAPM(Gui, showPopupAnnouncement),
@ -1327,6 +1331,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,getSpecificRef),
WRAPM(Job,getHolder),
WRAPM(Job,getWorker),
WRAPM(Job,setJobCooldown),
WRAPM(Job,removeWorker),
WRAPM(Job,checkBuildingsNow),
WRAPM(Job,checkDesignationsNow),
WRAPM(Job,isSuitableItem),
@ -1387,6 +1393,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getEffectiveSkill),
WRAPM(Units, getExperience),
WRAPM(Units, computeMovementSpeed),
WRAPM(Units, computeSlowdownFactor),
WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName),
WRAPM(Units, getProfessionColor),

@ -35,6 +35,8 @@ distribution.
#include "df/init.h"
#include "df/ui.h"
#include "df/announcement_type.h"
#include "df/announcement_flags.h"
#include "df/unit_report_type.h"
namespace df {
struct viewscreen;
@ -97,13 +99,20 @@ namespace DFHack
DFHACK_EXPORT bool any_building_hotkey(df::viewscreen *top);
DFHACK_EXPORT df::building *getSelectedBuilding(color_ostream &out, bool quiet = false);
// Low-level API that gives full control over announcements and reports
DFHACK_EXPORT void writeToGamelog(std::string message);
DFHACK_EXPORT int makeAnnouncement(df::announcement_type type, df::announcement_flags mode, df::coord pos, std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT bool addCombatReport(df::unit *unit, df::unit_report_type slot, int report_index);
DFHACK_EXPORT bool addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index);
// Show a plain announcement, or a titan-style popup message
DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showPopupAnnouncement(std::string message, int color = 7, bool bright = true);
// Show an announcement with effects determined by announcements.txt
DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL);
/*
* Cursor and window coords

@ -63,6 +63,9 @@ namespace DFHack
DFHACK_EXPORT df::building *getHolder(df::job *job);
DFHACK_EXPORT df::unit *getWorker(df::job *job);
DFHACK_EXPORT void setJobCooldown(df::building *workshop, df::unit *worker, int cooldown = 100);
DFHACK_EXPORT bool removeWorker(df::job *job, int cooldown = 100);
// Instruct the game to check and assign workers
DFHACK_EXPORT void checkBuildingsNow();
DFHACK_EXPORT void checkDesignationsNow();

@ -241,6 +241,7 @@ DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int getExperience(df::unit *unit, df::job_skill skill_id, bool total = false);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
DFHACK_EXPORT float computeSlowdownFactor(df::unit *unit);
struct NoblePosition {
df::historical_entity *entity;

@ -457,6 +457,14 @@ function getBuildingCenter(building)
return xyz2pos(building.centerx, building.centery, building.z)
end
function getItemDescription(item,mode)
return call_with_string(item, 'getItemDescription', mode or 0)
end
function getItemDescriptionPrefix(item,mode)
return call_with_string(item, 'getItemDescriptionPrefix', mode or 0)
end
-- Split the string by the given delimiter
function split_string(self, delimiter)
local result = { }

@ -87,6 +87,8 @@ using namespace DFHack;
#include "df/announcements.h"
#include "df/stop_depart_condition.h"
#include "df/route_stockpile_link.h"
#include "df/game_mode.h"
#include "df/unit.h"
using namespace df::enums;
using df::global::gview;
@ -97,6 +99,7 @@ using df::global::world;
using df::global::selection_rect;
using df::global::ui_menu_width;
using df::global::ui_area_map_width;
using df::global::gamemode;
static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx)
{
@ -1080,13 +1083,26 @@ df::building *Gui::getSelectedBuilding(color_ostream &out, bool quiet)
//
static void doShowAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
) {
DFHACK_EXPORT void Gui::writeToGamelog(std::string message)
{
if (message.empty())
return;
std::ofstream fseed("gamelog.txt", std::ios::out | std::ios::app);
if(fseed.is_open())
fseed << message << std::endl;
fseed.close();
}
DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announcement_flags flags, df::coord pos, std::string message, int color, bool bright)
{
using df::global::world;
using df::global::cur_year;
using df::global::cur_year_tick;
if (message.empty())
return -1;
int year = 0, year_time = 0;
if (cur_year && cur_year_tick)
@ -1102,6 +1118,31 @@ static void doShowAnnouncement(
year_time = last->time;
}
// Apply the requested effects
writeToGamelog(message);
if (flags.bits.DO_MEGA || flags.bits.PAUSE || flags.bits.RECENTER)
{
resetDwarfmodeView(flags.bits.DO_MEGA || flags.bits.PAUSE);
if (flags.bits.RECENTER && pos.isValid())
revealInDwarfmodeMap(pos, true);
if (flags.bits.DO_MEGA)
showPopupAnnouncement(message, color, bright);
}
bool display = false;
if (gamemode == NULL)
display = flags.bits.A_DISPLAY || flags.bits.D_DISPLAY;
else if (*gamemode == game_mode::ADVENTURE)
display = flags.bits.A_DISPLAY;
else
display = flags.bits.D_DISPLAY;
// Generate the report objects
int report_idx = world->status.reports.size();
bool continued = false;
while (!message.empty())
@ -1117,7 +1158,6 @@ static void doShowAnnouncement(
new_rep->time = year_time;
new_rep->flags.bits.continuation = continued;
new_rep->flags.bits.announcement = true;
int size = std::min(message.size(), (size_t)73);
new_rep->text = message.substr(0, size);
@ -1129,20 +1169,114 @@ static void doShowAnnouncement(
new_rep->id = world->status.next_report_id++;
world->status.reports.push_back(new_rep);
world->status.announcements.push_back(new_rep);
world->status.display_timer = 2000;
if (display)
{
new_rep->flags.bits.announcement = true;
world->status.announcements.push_back(new_rep);
world->status.display_timer = 2000;
}
}
return report_idx;
}
bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_index)
{
using df::global::world;
CHECK_INVALID_ARGUMENT(is_valid_enum_item(slot));
auto &vec = world->status.reports;
auto report = vector_get(vec, report_index);
if (!unit || !report)
return false;
// Check that it is a new report
auto &rvec = unit->reports.log[slot];
if (!rvec.empty() && rvec.back() >= report->id)
return false;
// Add the report
rvec.push_back(report->id);
unit->reports.last_year[slot] = report->year;
unit->reports.last_year_tick[slot] = report->time;
switch (slot) {
case unit_report_type::Combat:
world->status.flags.bits.combat = true;
break;
case unit_report_type::Hunting:
world->status.flags.bits.hunting = true;
break;
case unit_report_type::Sparring:
world->status.flags.bits.sparring = true;
break;
}
// And all the continuation lines
for (size_t i = report_index+1; i < vec.size() && vec[i]->flags.bits.continuation; i++)
rvec.push_back(vec[i]->id);
return true;
}
bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index)
{
using df::global::world;
auto &vec = world->status.reports;
auto report = vector_get(vec, report_index);
if (!unit || !report)
return false;
bool ok = false;
if (mode.bits.UNIT_COMBAT_REPORT)
{
if (unit->flags2.bits.sparring)
ok |= addCombatReport(unit, unit_report_type::Sparring, report_index);
else if (unit->job.current_job && unit->job.current_job->job_type == job_type::Hunt)
ok |= addCombatReport(unit, unit_report_type::Hunting, report_index);
else
ok |= addCombatReport(unit, unit_report_type::Combat, report_index);
}
if (mode.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE)
{
FOR_ENUM_ITEMS(unit_report_type, slot)
{
if (!unit->reports.log[slot].empty() &&
unit->reports.last_year[slot] == report->year &&
(report->time - unit->reports.last_year_tick[slot]) <= 500)
{
ok |= addCombatReport(unit, slot, report_index);
}
}
}
return ok;
}
void Gui::showAnnouncement(std::string message, int color, bool bright)
{
doShowAnnouncement(df::announcement_type(0), df::coord(), message, color, bright);
df::announcement_flags mode(0);
mode.bits.D_DISPLAY = mode.bits.A_DISPLAY = true;
makeAnnouncement(df::announcement_type(0), mode, df::coord(), message, color, bright);
}
void Gui::showZoomAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
) {
doShowAnnouncement(type, pos, message, color, bright);
df::announcement_flags mode(0);
mode.bits.D_DISPLAY = mode.bits.A_DISPLAY = true;
makeAnnouncement(type, mode, pos, message, color, bright);
}
void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
@ -1157,26 +1291,21 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
}
void Gui::showAutoAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
df::announcement_type type, df::coord pos, std::string message, int color, bool bright,
df::unit *unit1, df::unit *unit2
) {
using df::global::announcements;
df::announcement_flags flags;
df::announcement_flags flags(0);
flags.bits.D_DISPLAY = flags.bits.A_DISPLAY = true;
if (is_valid_enum_item(type) && announcements)
flags = announcements->flags[type];
doShowAnnouncement(type, pos, message, color, bright);
if (flags.bits.DO_MEGA || flags.bits.PAUSE || flags.bits.RECENTER)
{
resetDwarfmodeView(flags.bits.DO_MEGA || flags.bits.PAUSE);
if (flags.bits.RECENTER && pos.isValid())
revealInDwarfmodeMap(pos, true);
}
int id = makeAnnouncement(type, flags, pos, message, color, bright);
if (flags.bits.DO_MEGA)
showPopupAnnouncement(message, color, bright);
addCombatReportAuto(unit1, flags, id);
addCombatReportAuto(unit2, flags, id);
}
df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)

@ -44,6 +44,8 @@ using namespace std;
#include "DataDefs.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/building.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/job_list_link.h"
@ -60,13 +62,13 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepEverything)
CHECK_NULL_POINTER(job);
df::job *pnew = new df::job(*job);
if ( !keepEverything ) {
// Clean out transient fields
pnew->flags.whole = 0;
pnew->flags.bits.repeat = job->flags.bits.repeat;
pnew->flags.bits.suspend = job->flags.bits.suspend;
pnew->completion_timer = -1;
}
pnew->list_link = NULL;
@ -75,24 +77,24 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepEverything)
//pnew->specific_refs.clear();
pnew->general_refs.clear();
//pnew->job_items.clear();
if ( keepEverything ) {
for ( int a = 0; a < pnew->items.size(); a++ )
for ( size_t a = 0; a < pnew->items.size(); a++ )
pnew->items[a] = new df::job_item_ref(*pnew->items[a]);
for ( int a = 0; a < pnew->specific_refs.size(); a++ )
for ( size_t a = 0; a < pnew->specific_refs.size(); a++ )
pnew->specific_refs[a] = new df::specific_ref(*pnew->specific_refs[a]);
} else {
pnew->items.clear();
pnew->specific_refs.clear();
}
for ( int a = 0; a < pnew->job_items.size(); a++ )
for ( size_t a = 0; a < pnew->job_items.size(); a++ )
pnew->job_items[a] = new df::job_item(*pnew->job_items[a]);
for ( int a = 0; a < job->general_refs.size(); a++ )
for ( size_t a = 0; a < job->general_refs.size(); a++ )
if ( keepEverything || job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
pnew->general_refs.push_back(job->general_refs[a]->clone());
return pnew;
}
@ -106,16 +108,16 @@ void DFHack::Job::deleteJobStruct(df::job *job, bool keptEverything)
assert(!job->list_link && job->items.empty() && job->specific_refs.empty());
else
assert(!job->list_link);
if ( keptEverything ) {
for ( int a = 0; a < job->items.size(); a++ )
for ( size_t a = 0; a < job->items.size(); a++ )
delete job->items[a];
for ( int a = 0; a < job->specific_refs.size(); a++ )
for ( size_t a = 0; a < job->specific_refs.size(); a++ )
delete job->specific_refs[a];
}
for ( int a = 0; a < job->job_items.size(); a++ )
for ( size_t a = 0; a < job->job_items.size(); a++ )
delete job->job_items[a];
for ( int a = 0; a < job->general_refs.size(); a++ )
for ( size_t a = 0; a < job->general_refs.size(); a++ )
delete job->general_refs[a];
delete job;
@ -286,6 +288,63 @@ df::unit *DFHack::Job::getWorker(df::job *job)
return NULL;
}
void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int cooldown)
{
CHECK_NULL_POINTER(workshop);
CHECK_NULL_POINTER(worker);
if (cooldown <= 0)
return;
int idx = linear_index(workshop->job_claim_suppress, &df::building::T_job_claim_suppress::unit, worker);
if (idx < 0)
{
auto obj = new df::building::T_job_claim_suppress;
obj->unit = worker;
obj->timer = cooldown;
workshop->job_claim_suppress.push_back(obj);
}
else
{
auto obj = workshop->job_claim_suppress[idx];
obj->timer = std::max(obj->timer, cooldown);
}
}
bool DFHack::Job::removeWorker(df::job *job, int cooldown)
{
CHECK_NULL_POINTER(job);
if (job->flags.bits.special)
return false;
auto holder = getHolder(job);
if (!holder || linear_index(holder->jobs,job) < 0)
return false;
for (size_t i = 0; i < job->general_refs.size(); i++)
{
VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->general_refs[i]);
if (!ref)
continue;
auto worker = ref->getUnit();
if (!worker || worker->job.current_job != job)
return false;
setJobCooldown(holder, worker, cooldown);
vector_erase_at(job->general_refs, i);
worker->job.current_job = NULL;
delete ref;
return true;
}
return false;
}
void DFHack::Job::checkBuildingsNow()
{
if (df::global::process_jobs)

@ -54,6 +54,8 @@ using namespace std;
#include "df/unit_soul.h"
#include "df/nemesis_record.h"
#include "df/historical_entity.h"
#include "df/entity_raw.h"
#include "df/entity_raw_flags.h"
#include "df/historical_figure.h"
#include "df/historical_figure_info.h"
#include "df/entity_position.h"
@ -1060,6 +1062,8 @@ int Units::computeMovementSpeed(df::unit *unit)
{
using namespace df::enums::physical_attribute_type;
CHECK_NULL_POINTER(unit);
/*
* Pure reverse-engineered computation of unit _slowness_,
* i.e. number of ticks to move * 100.
@ -1264,6 +1268,54 @@ int Units::computeMovementSpeed(df::unit *unit)
return std::min(10000, std::max(0, speed));
}
static bool entityRawFlagSet(int civ_id, df::entity_raw_flags flag)
{
auto entity = df::historical_entity::find(civ_id);
return entity && entity->entity_raw && entity->entity_raw->flags.is_set(flag);
}
float Units::computeSlowdownFactor(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
/*
* These slowdowns are actually done by skipping a move if random(x) != 0, so
* it follows the geometric distribution. The mean expected slowdown is x.
*/
float coeff = 1.0f;
if (!unit->job.hunt_target && (!gamemode || *gamemode == game_mode::DWARF))
{
if (!unit->flags1.bits.marauder &&
casteFlagSet(unit->race, unit->caste, caste_raw_flags::MEANDERER) &&
!(unit->relations.following && isCitizen(unit)) &&
linear_index(unit->inventory, &df::unit_inventory_item::mode,
df::unit_inventory_item::Hauled) < 0)
{
coeff *= 4.0f;
}
if (unit->relations.group_leader_id < 0 &&
unit->flags1.bits.active_invader &&
!unit->job.current_job && !unit->flags3.bits.no_meandering &&
unit->profession != profession::THIEF && unit->profession != profession::MASTER_THIEF &&
!entityRawFlagSet(unit->civ_id, entity_raw_flags::ITEM_THIEF))
{
coeff *= 3.0f;
}
}
if (unit->flags3.bits.floundering)
{
coeff *= 3.0f;
}
return coeff;
}
static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b)
{
if (a.position->precedence < b.position->precedence)

@ -54,9 +54,15 @@ local _ENV = mkmodule('plugins.siege-engine')
]]
local utils = require 'utils'
Z_STEP_COUNT = 15
Z_STEP = 1/31
ANNOUNCEMENT_FLAGS = {
UNIT_COMBAT_REPORT = true
}
function getMetrics(engine, path)
path.metrics = path.metrics or projPathMetrics(engine, path)
return path.metrics
@ -127,21 +133,41 @@ function saveRecent(unit)
end
function getBaseUnitWeight(unit)
if dfhack.units.isCitizen(unit) then
return -10
elseif unit.flags1.diplomat or unit.flags1.merchant then
return -2
elseif unit.flags1.tame and unit.civ_id == df.global.ui.civ_id then
return -1
local flags1 = unit.flags1
local rv = 1
if unit.mood == df.mood_type.Berserk
or dfhack.units.isOpposedToLife(unit)
or dfhack.units.isCrazed(unit)
then
rv = rv + 1
else
local rv = 1
if unit.flags1.marauder then rv = rv + 0.5 end
if unit.flags1.active_invader then rv = rv + 1 end
if unit.flags1.invader_origin then rv = rv + 1 end
if unit.flags1.invades then rv = rv + 1 end
if unit.flags1.hidden_ambusher then rv = rv + 1 end
return rv
if dfhack.units.isCitizen(unit) then
return -30
elseif flags1.diplomat or flags1.merchant or flags1.forest then
return -5
elseif flags1.tame and unit.civ_id == df.global.ui.civ_id then
return -1
end
end
if flags1.marauder then rv = rv + 0.5 end
if flags1.active_invader then rv = rv + 1 end
if flags1.invader_origin then rv = rv + 1 end
if flags1.invades then rv = rv + 1 end
if flags1.hidden_ambusher then rv = rv + 1 end
if unit.counters.unconscious > 0 then
rv = rv * 0.3
elseif unit.job.hunt_target then
rv = rv * 3
elseif unit.job.destroy_target then
rv = rv * 2
elseif unit.relations.group_leader_id < 0 and not flags1.rider then
rv = rv * 1.5
end
return rv
end
function getUnitWeight(unit)
@ -208,8 +234,61 @@ function pickUniqueTargets(reachable)
return unique
end
function doAimProjectile(engine, item, target_min, target_max, skill)
print(item, df.skill_rating[skill])
function describeUnit(unit)
local desc = dfhack.units.getProfessionName(unit)
local name = dfhack.units.getVisibleName(unit)
if name.has_name then
return desc .. ' ' .. dfhack.TranslateName(name)
end
return desc
end
function produceCombatReport(operator, item, target)
local msg = describeUnit(operator) .. ' launches ' ..
utils.getItemDescriptionPrefix(item) ..
utils.getItemDescription(item) ..
' at '
local pos = operator.pos
if target then
local units = target.units
for i,v in ipairs(units) do
if i > 1 then
if i < #units then
msg = msg .. ', '
elseif i > 2 then
msg = msg .. ', and '
else
msg = msg .. ' and '
end
end
msg = msg .. describeUnit(v)
end
msg = msg .. '!'
pos = target.pos
else
msg = msg .. 'the target area.'
end
local id = dfhack.gui.makeAnnouncement(
df.announcement_type.COMBAT_STRIKE_DETAILS,
ANNOUNCEMENT_FLAGS, pos, msg, COLOR_CYAN, true
)
dfhack.gui.addCombatReport(operator, df.unit_report_type.Hunting, id)
if target then
for i,v in ipairs(target.units) do
dfhack.gui.addCombatReportAuto(v, ANNOUNCEMENT_FLAGS, id)
end
end
end
function doAimProjectile(engine, item, target_min, target_max, operator, skill)
--print(item, df.skill_rating[skill])
local targets = proposeUnitHits(engine)
local reachable = findReachableTargets(engine, targets)
@ -219,11 +298,15 @@ function doAimProjectile(engine, item, target_min, target_max, skill)
if #unique > 0 then
local cnt = math.max(math.min(#unique,5), math.min(10, math.floor(#unique/2)))
local rnd = math.random(cnt)
for _,u in ipairs(unique[rnd].units) do
local target = unique[rnd]
for _,u in ipairs(target.units) do
saveRecent(u)
end
return unique[rnd].path
produceCombatReport(operator, item, target)
return target.path
end
produceCombatReport(operator, item, nil)
end
return _ENV

@ -55,6 +55,7 @@
#include "df/strain_type.h"
#include "df/material.h"
#include "df/flow_type.h"
#include "df/invasion_info.h"
#include "MiscUtils.h"
@ -757,20 +758,20 @@ static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld
return &engine->profile;
}
static int getOperatorSkill(df::building_siegeenginest *bld, bool force = false)
static df::unit *getOperatorUnit(df::building_siegeenginest *bld, bool force = false)
{
CHECK_NULL_POINTER(bld);
auto engine = find_engine(bld);
if (!engine)
return 0;
return NULL;
if (engine->operator_id != -1 &&
(world->frame_counter - engine->operator_frame) <= 5)
{
auto op_unit = df::unit::find(engine->operator_id);
if (op_unit)
return Units::getEffectiveSkill(op_unit, job_skill::SIEGEOPERATE);
return op_unit;
}
if (force)
@ -781,10 +782,10 @@ static int getOperatorSkill(df::building_siegeenginest *bld, bool force = false)
auto &active = world->units.active;
for (size_t i = 0; i < active.size(); i++)
if (active[i]->pos == engine->center && Units::isCitizen(active[i]))
return Units::getEffectiveSkill(active[i], job_skill::SIEGEOPERATE);
return active[i];
}
return 0;
return NULL;
}
/*
@ -1206,6 +1207,7 @@ struct UnitPath {
{
float time = unit->counters.job_counter+0.5f;
float speed = Units::computeMovementSpeed(unit)/100.0f;
float slowdown = Units::computeSlowdownFactor(unit);
if (unit->counters.unconscious > 0)
time += unit->counters.unconscious;
@ -1217,9 +1219,14 @@ struct UnitPath {
continue;
float delay = speed;
// Diagonal movement
if (new_pos.x != pos.x && new_pos.y != pos.y)
delay *= 362.0/256.0;
// Meandering slowdown
delay += (slowdown - 1) * speed;
path[time] = pos;
pos = new_pos;
time += delay + 1;
@ -1333,9 +1340,11 @@ static bool canTargetUnit(df::unit *unit)
CHECK_NULL_POINTER(unit);
if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly ||
unit->flags1.bits.caged ||
unit->flags1.bits.hidden_in_ambush)
unit->flags1.bits.left ||
unit->flags1.bits.incoming ||
unit->flags1.bits.hidden_in_ambush ||
unit->flags3.bits.ghostly)
return false;
return true;
@ -1453,6 +1462,55 @@ static int computeNearbyWeight(lua_State *L)
return 0;
}
static bool isTired(df::unit *worker)
{
return worker->counters2.exhaustion >= 1000 ||
worker->counters2.thirst_timer >= 25000 ||
worker->counters2.hunger_timer >= 50000 ||
worker->counters2.sleepiness_timer >= 57600;
}
static void releaseTiredWorker(EngineInfo *engine, df::job *job, df::unit *worker)
{
// If not in siege
auto &sieges = ui->invasions.list;
for (size_t i = 0; i < sieges.size(); i++)
if (sieges[i]->flags.bits.active)
return;
// And there is a free replacement
auto &others = world->units.active;
for (size_t i = 0; i < others.size(); i++)
{
auto unit = others[i];
if (unit == worker ||
unit->job.current_job || !unit->status.labors[unit_labor::SIEGEOPERATE] ||
!Units::isCitizen(unit) || Units::getMiscTrait(unit, misc_trait_type::OnBreak) ||
isTired(unit) || !Maps::canWalkBetween(job->pos, unit->pos))
continue;
int skill2 = Units::getEffectiveSkill(unit, job_skill::SIEGEOPERATE);
if (skill2 >= engine->profile.min_level && skill2 <= engine->profile.max_level)
{
// Remove the worker and request a recheck
if (Job::removeWorker(job))
{
color_ostream_proxy out(Core::getInstance().getConsole());
out.print("Released tired operator %d from siege engine.\n", worker->id);
if (df::global::process_jobs)
*df::global::process_jobs = true;
}
return;
}
}
}
/*
* Projectile hook
*/
@ -1578,7 +1636,8 @@ struct projectile_hook : df::proj_itemst {
color_ostream &out = *Lua::GetOutput(L);
auto proj = (projectile_hook*)lua_touserdata(L, 1);
auto engine = (EngineInfo*)lua_touserdata(L, 2);
int skill = lua_tointeger(L, 3);
auto unit = (df::unit*)lua_touserdata(L, 3);
int skill = lua_tointeger(L, 4);
if (!Lua::PushModulePublic(out, L, "plugins.siege-engine", "doAimProjectile"))
luaL_error(L, "Projectile aiming AI not available");
@ -1587,9 +1646,10 @@ struct projectile_hook : df::proj_itemst {
Lua::Push(L, proj->item);
Lua::Push(L, engine->target.first);
Lua::Push(L, engine->target.second);
Lua::PushDFObject(L, unit);
Lua::Push(L, skill);
lua_call(L, 5, 1);
lua_call(L, 6, 1);
if (lua_isnil(L, -1))
proj->aimAtArea(engine, skill);
@ -1613,7 +1673,8 @@ struct projectile_hook : df::proj_itemst {
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
int skill = getOperatorSkill(engine->bld, true);
df::unit *op_unit = getOperatorUnit(engine->bld, true);
int skill = op_unit ? Units::getEffectiveSkill(op_unit, job_skill::SIEGEOPERATE) : 0;
// Dabbling can't aim
if (skill < skill_rating::Novice)
@ -1623,9 +1684,10 @@ struct projectile_hook : df::proj_itemst {
lua_pushcfunction(L, safeAimProjectile);
lua_pushlightuserdata(L, this);
lua_pushlightuserdata(L, engine);
lua_pushlightuserdata(L, op_unit);
lua_pushinteger(L, skill);
if (!Lua::Core::SafeCall(out, 3, 0))
if (!Lua::Core::SafeCall(out, 4, 0))
aimAtArea(engine, skill);
}
@ -1786,6 +1848,7 @@ struct building_hook : df::building_siegeenginest {
{
auto job = jobs[0];
bool save_op = false;
bool load_op = false;
switch (job->job_type)
{
@ -1820,12 +1883,18 @@ struct building_hook : df::building_siegeenginest {
// fallthrough
case job_type::LoadBallista:
load_op = true;
case job_type::FireCatapult:
case job_type::FireBallista:
if (auto worker = Job::getWorker(job))
{
engine->operator_id = worker->id;
engine->operator_frame = world->frame_counter;
if (action == PrepareToFire && !load_op &&
(world->frame_counter%100) == 0 && isTired(worker))
releaseTiredWorker(engine, job, worker);
}
break;

@ -10,18 +10,12 @@ local seasons = {
local args = {...}
local function write_gamelog(msg)
local log = io.open('gamelog.txt', 'a')
log:write(msg.."\n")
log:close()
end
if args[1] == 'disable' then
dfhack.onStateChange[_ENV] = nil
else
dfhack.onStateChange[_ENV] = function(op)
if op == SC_WORLD_LOADED then
write_gamelog(seasons[df.global.cur_season]..' has arrived on the calendar.')
dfhack.gui.writeToGamelog(seasons[df.global.cur_season]..' has arrived on the calendar.')
end
end
end