From d506dd713717aedb4ef98a087834845c13ac92c7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 16 Nov 2012 22:51:07 +0400 Subject: [PATCH] Add a tweak to speed up melee squad training. --- NEWS | 2 + dfhack.init-example | 3 + library/xml | 2 +- plugins/tweak.cpp | 189 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 48aaf26a2..39b4ccf02 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + New tweaks: + - tweak military-training: speed up melee squad training up to 10x (depends on unit count). New scripts: - binpatch: the same as the stand-alone binpatch.exe, but works at runtime. - region-pops: displays animal populations of the region and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index 885849c33..8fafa4cf4 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -133,6 +133,9 @@ tweak military-stable-assign # in same list, color units already assigned to squads in brown & green tweak military-color-assigned +# remove inverse dependency of squad training speed on unit list size and use more sparring +tweak military-training + ####################################################### # Apply binary patches at runtime # # # diff --git a/library/xml b/library/xml index 327a9662b..9b4dc47a5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 327a9662be81627ebcbb3aea11ffbca3e536b7ee +Subproject commit 9b4dc47a54c8b15db2f30fbf926deb8c1bf992f6 diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index b4a435421..c2309a3ee 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -51,6 +51,12 @@ #include "df/squad_position.h" #include "df/job.h" #include "df/general_ref_building_holderst.h" +#include "df/unit_health_info.h" +#include "df/activity_entry.h" +#include "df/activity_event_combat_trainingst.h" +#include "df/activity_event_individual_skill_drillst.h" +#include "df/activity_event_skill_demonstrationst.h" +#include "df/activity_event_sparringst.h" #include @@ -128,6 +134,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector counters2.exhaustion <= 2000 && // actually 4000, but leave a gap + (unit->status2.able_grasp_impair > 0 || unit->status2.able_grasp == 0) && + (!unit->health || (unit->health->flags.whole&0x7FF) == 0) && + (!unit->job.current_job || unit->job.current_job != job_type::Rest); +} + +struct military_training_ct_hook : df::activity_event_combat_trainingst { + typedef df::activity_event_combat_trainingst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) + { + auto act = df::activity_entry::find(activity_id); + int cur_neid = act ? act->next_event_id : 0; + int cur_oc = organize_counter; + + INTERPOSE_NEXT(process)(unit); + + // Shorten the time it takes to organize stuff, so that in + // reality it remains the same instead of growing proportionally + // to the unit count. + if (organize_counter > cur_oc && organize_counter > 0) + organize_counter = adjust_unit_divisor(organize_counter); + + if (act && act->next_event_id > cur_neid) + { + // New events were added. Check them. + for (size_t i = 0; i < act->events.size(); i++) + { + auto event = act->events[i]; + if (event->flags.bits.dismissed || event->event_id < cur_neid) + continue; + + if (auto sp = strict_virtual_cast(event)) + { + // Sparring has a problem in that all of its participants decrement + // the countdown variable. Fix this by multiplying it by the member count. + sp->countdown = sp->countdown * sp->participants.hist_figure_ids.size(); + } + else if (auto sd = strict_virtual_cast(event)) + { + // Adjust initial counter values + sd->train_countdown = adjust_unit_divisor(sd->train_countdown); + sd->wait_countdown = adjust_unit_divisor(sd->wait_countdown); + + // Check if the game selected the most skilled unit as the teacher + auto &units = sd->participants.participant_ids; + int maxv = -1, cur_xp = -1, minv = 0; + int best = -1; + size_t spar = 0; + + for (size_t j = 0; j < units.size(); j++) + { + auto unit = df::unit::find(units[j]); + if (!unit) continue; + int xp = Units::getExperience(unit, sd->skill, true); + if (units[j] == sd->unit_id) + cur_xp = xp; + if (j == 0 || xp < minv) + minv = xp; + if (xp > maxv) { + maxv = xp; + best = j; + } + if (can_spar(unit)) + spar++; + } + + color_ostream_proxy out(Core::getInstance().getConsole()); + + // If the xp gap is low, sometimes replace with sparring + if ((maxv - minv) < 64*15 && spar == units.size() && + random_int(20) >= 5 + (maxv-minv)/64) + { + out.print("Replacing demonstration with sparring.\n"); + + if (auto spar = df::allocate()) + { + spar->event_id = sd->event_id; + spar->activity_id = sd->activity_id; + spar->parent_event_id = sd->parent_event_id; + spar->flags = sd->flags; + spar->participants = sd->participants; + spar->building_id = sd->building_id; + spar->countdown = 300*units.size(); + + delete sd; + act->events[i] = spar; + + continue; + } + } + + // If the teacher has less xp than somebody else, switch + if (best >= 0 && maxv > cur_xp) + { + out.print("Replacing teacher %d (%d xp) with %d (%d xp)\n", + sd->unit_id, cur_xp, units[best], maxv); + + sd->hist_figure_id = sd->participants.hist_figure_ids[best]; + sd->unit_id = units[best]; + } + } + } + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_training_ct_hook, process); + +struct military_training_sd_hook : df::activity_event_skill_demonstrationst { + typedef df::activity_event_skill_demonstrationst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) + { + int cur_oc = organize_counter; + int cur_tc = train_countdown; + + INTERPOSE_NEXT(process)(unit); + + // Shorten the counters if they changed + if (organize_counter > cur_oc && organize_counter > 0) + organize_counter = adjust_unit_divisor(organize_counter); + if (train_countdown > cur_tc) + train_countdown = adjust_unit_divisor(train_countdown); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_training_sd_hook, process); + +static bool is_done(df::activity_event *event, df::unit *unit) +{ + return event->flags.bits.dismissed || + binsearch_index(event->participants.participant_ids, unit->id) < 0; +} + +struct military_training_sp_hook : df::activity_event_sparringst { + typedef df::activity_event_sparringst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) + { + INTERPOSE_NEXT(process)(unit); + + // Since there are no counters to fix, repeat the call + int cnt = (DF_GLOBAL_FIELD(ui, unit_action_divisor, 10)+5) / 10; + for (int i = 1; i < cnt && !is_done(this, unit); i++) + INTERPOSE_NEXT(process)(unit); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_training_sp_hook, process); + +struct military_training_id_hook : df::activity_event_individual_skill_drillst { + typedef df::activity_event_individual_skill_drillst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) + { + INTERPOSE_NEXT(process)(unit); + + // Since there are no counters to fix, repeat the call + int cnt = (DF_GLOBAL_FIELD(ui, unit_action_divisor, 10)+5) / 10; + for (int i = 1; i < cnt && !is_done(this, unit); i++) + INTERPOSE_NEXT(process)(unit); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_training_id_hook, process); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -835,6 +1017,13 @@ static command_result tweak(color_ostream &out, vector ¶meters) { enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); } + else if (cmd == "military-training") + { + enable_hook(out, INTERPOSE_HOOK(military_training_ct_hook, process), parameters); + enable_hook(out, INTERPOSE_HOOK(military_training_sd_hook, process), parameters); + enable_hook(out, INTERPOSE_HOOK(military_training_sp_hook, process), parameters); + enable_hook(out, INTERPOSE_HOOK(military_training_id_hook, process), parameters); + } else return CR_WRONG_USAGE;