|  |  |  | @ -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 <stdlib.h> | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -128,6 +134,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi | 
		
	
		
			
				|  |  |  |  |         "  tweak military-color-assigned [disable]\n" | 
		
	
		
			
				|  |  |  |  |         "    Color squad candidates already assigned to other squads in brown/green\n" | 
		
	
		
			
				|  |  |  |  |         "    to make them stand out more in the list.\n" | 
		
	
		
			
				|  |  |  |  |         "  tweak military-training [disable]\n" | 
		
	
		
			
				|  |  |  |  |         "    Speed up melee squad training, removing inverse dependency on unit count.\n" | 
		
	
		
			
				|  |  |  |  |     )); | 
		
	
		
			
				|  |  |  |  |     return CR_OK; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
	
		
			
				
					|  |  |  | @ -662,6 +670,180 @@ struct military_assign_hook : df::viewscreen_layer_militaryst { | 
		
	
		
			
				|  |  |  |  | IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, feed); | 
		
	
		
			
				|  |  |  |  | IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Unit updates are executed based on an action divisor variable,
 | 
		
	
		
			
				|  |  |  |  | // which is computed from the alive unit count and has range 10-100.
 | 
		
	
		
			
				|  |  |  |  | static int adjust_unit_divisor(int value) { | 
		
	
		
			
				|  |  |  |  |     return value*10/DF_GLOBAL_FIELD(ui, unit_action_divisor, 10); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | static bool can_spar(df::unit *unit) { | 
		
	
		
			
				|  |  |  |  |     return unit->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<df::activity_event_sparringst>(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<df::activity_event_skill_demonstrationst>(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<df::activity_event_sparringst>()) | 
		
	
		
			
				|  |  |  |  |                         { | 
		
	
		
			
				|  |  |  |  |                             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 <string> ¶meters) | 
		
	
		
			
				|  |  |  |  | { | 
		
	
		
			
				|  |  |  |  |     if (vector_get(parameters, 1) == "disable") | 
		
	
	
		
			
				
					|  |  |  | @ -835,6 +1017,13 @@ static command_result tweak(color_ostream &out, vector <string> ¶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; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | 
 |