4545#include " fatal.h"
4646#include " debughandler.h"
4747#include " asserthandler.h"
48+ #include " optionsext.h"
49+ #include " object.h"
50+ #include " house.h"
51+ #include " technotype.h"
52+ #include " building.h"
53+ #include " buildingtype.h"
54+
4855#include < timeapi.h>
4956
5057#include " hooker.h"
5158#include " hooker_macros.h"
59+ #include " technotypeext.h"
5260#include " uicontrol.h"
5361
5462
@@ -65,8 +73,23 @@ class TacticalExt : public Tactical
6573{
6674public:
6775 void _Draw_Band_Box ();
76+ void _Select_These (Rect& rect, void (*selection_func)(ObjectClass* obj));
77+
78+ public:
79+
80+ /* *
81+ * Static variables for selection filtering, need to be static
82+ * so that the selection predicate can use them.
83+ */
84+ static bool SelectionContainsNonCombatants;
85+ static int SelectedCount;
86+ static bool FilterSelection;
6887};
6988
89+ bool TacticalExt::SelectionContainsNonCombatants = false ;
90+ int TacticalExt::SelectedCount = 0 ;
91+ bool TacticalExt::FilterSelection = false ;
92+
7093
7194/* *
7295 * Reimplements Tactical::Draw_Band_Box.
@@ -185,6 +208,217 @@ void TacticalExt::_Draw_Band_Box()
185208}
186209
187210
211+ /* *
212+ * Helper function.
213+ * Checks whether a specific object should be filtered
214+ * out from selection if the selection includes combatants.
215+ */
216+ static bool Should_Exclude_From_Selection (ObjectClass* obj)
217+ {
218+ /* *
219+ * Don't exclude objects that we don't own.
220+ */
221+ if (obj->Owning_House () != nullptr && !obj->Owning_House ()->IsPlayerControl ) {
222+ return false ;
223+ }
224+
225+ /* *
226+ * Exclude objects that aren't a selectable combatant per rules.
227+ */
228+ if (obj->Is_Techno ()) {
229+ return Extension::Fetch<TechnoTypeClassExtension>(obj->Techno_Type_Class ())->FilterFromBandBoxSelection ;
230+ }
231+
232+ return false ;
233+ }
234+
235+
236+ /* *
237+ * Filters the selection from any non-combatants.
238+ *
239+ * @author: Petroglyph (Remaster), Rampastring, ZivDero
240+ */
241+ static void Filter_Selection ()
242+ {
243+ if (!OptionsExtension->FilterBandBoxSelection ) {
244+ return ;
245+ }
246+
247+ bool any_to_exclude = false ;
248+ bool all_to_exclude = true ;
249+
250+ for (int i = 0 ; i < CurrentObjects.Count (); i++) {
251+ const bool exclude = Should_Exclude_From_Selection (CurrentObjects[i]);
252+ any_to_exclude |= exclude;
253+ all_to_exclude &= exclude;
254+ }
255+
256+ if (any_to_exclude && !all_to_exclude) {
257+ for (int i = 0 ; i < CurrentObjects.Count (); i++) {
258+ if (Should_Exclude_From_Selection (CurrentObjects[i])) {
259+
260+ const int count_before = CurrentObjects.Count ();
261+ CurrentObjects[i]->Unselect ();
262+ const int count_after = CurrentObjects.Count ();
263+ if (count_after < count_before) {
264+ i--;
265+ }
266+ }
267+ }
268+ }
269+ }
270+
271+
272+ /* *
273+ * Checks if the player has currently any non-combatants selected.
274+ *
275+ * @author: ZivDero
276+ */
277+ static bool Has_NonCombatants_Selected ()
278+ {
279+ for (int i = 0 ; i < CurrentObjects.Count (); i++)
280+ {
281+ if (CurrentObjects[i]->Is_Techno () && Extension::Fetch<TechnoTypeClassExtension>(CurrentObjects[i]->Techno_Type_Class ())->FilterFromBandBoxSelection )
282+ return true ;
283+ }
284+
285+ return false ;
286+ }
287+
288+
289+ /* *
290+ * Reimplements Tactical::Select_These to filter non-combatants.
291+ *
292+ * @author: ZivDero
293+ */
294+ void TacticalExt::_Select_These (Rect& rect, void (*selection_func)(ObjectClass* obj))
295+ {
296+ SelectionContainsNonCombatants = Has_NonCombatants_Selected ();
297+ SelectedCount = CurrentObjects.Count ();
298+ FilterSelection = false ;
299+
300+ AllowVoice = true ;
301+
302+ if (rect.Width > 0 && rect.Height > 0 && DirtyObjectCount > 0 )
303+ {
304+ for (int i = 0 ; i < DirtyObjectCount; i++)
305+ {
306+ const auto dirty = DirtyObjects[i];
307+ if (dirty.Object && dirty.Object ->IsActive )
308+ {
309+ Point2D position = dirty.Position - field_5C;
310+ if (rect.Is_Within (position))
311+ {
312+ if (selection_func)
313+ {
314+ selection_func (dirty.Object );
315+ }
316+ else
317+ {
318+ bool is_selectable_building = false ;
319+ if (dirty.Object ->What_Am_I () == RTTI_BUILDING )
320+ {
321+ const auto bclass = static_cast <BuildingClass*>(dirty.Object )->Class ;
322+ if (bclass->UndeploysInto && !bclass->IsConstructionYard && !bclass->IsMobileWar )
323+ {
324+ is_selectable_building = true ;
325+ }
326+ }
327+
328+ HouseClass* owner = dirty.Object ->Owning_House ();
329+ if (owner && owner->Is_Player_Control ())
330+ {
331+ if (dirty.Object ->Class_Of ()->IsSelectable )
332+ {
333+ if (dirty.Object ->What_Am_I () != RTTI_BUILDING || is_selectable_building)
334+ {
335+ if (dirty.Object ->Select ())
336+ AllowVoice = false ;
337+ }
338+
339+ }
340+
341+ }
342+ }
343+ }
344+ }
345+ }
346+
347+ }
348+
349+ /* *
350+ * If player-controlled units are non-additively selected,
351+ * remove non-combatants if they aren't the only types of units selected
352+ */
353+ if (FilterSelection)
354+ Filter_Selection ();
355+
356+ AllowVoice = true ;
357+ }
358+
359+
360+ /* *
361+ * Band box selection predicate replacement.
362+ *
363+ * @author: ZivDero
364+ */
365+ static void Vinifera_Bandbox_Select (ObjectClass* obj)
366+ {
367+ HouseClass* house = obj->Owning_House ();
368+ BuildingClass* building = Target_As_Building (obj);
369+
370+ /* *
371+ * Don't select objects that we don't own.
372+ */
373+ if (!house || !house->Is_Player_Control ())
374+ return ;
375+
376+ /* *
377+ * Don't select objects that aren't selectable.
378+ */
379+ if (!obj->Class_Of ()->IsSelectable )
380+ return ;
381+
382+ /* *
383+ * Don't select buildings, unless it undeploys into something other than
384+ * a construction yard or a war factory (for example, a deploying artillery).
385+ */
386+ if (building && (!building->Class ->UndeploysInto || building->Class ->IsConstructionYard || building->Class ->IsMobileWar ))
387+ return ;
388+
389+ /* *
390+ * Don't select limboed objects.
391+ */
392+ if (obj->IsInLimbo )
393+ return ;
394+
395+ /* *
396+ * If this is a Techno that's not a combatant, and the selection isn't new and doesn't
397+ * already contain non-combatants, don't select it.
398+ */
399+ const TechnoClass* techno = Target_As_Techno (obj);
400+ if (techno && OptionsExtension->FilterBandBoxSelection
401+ && TacticalExt::SelectedCount > 0 && !TacticalExt::SelectionContainsNonCombatants
402+ && !WWKeyboard->Down (VK_ALT ))
403+ {
404+ const auto ext = Extension::Fetch<TechnoTypeClassExtension>(techno->Techno_Type_Class ());
405+ if (ext->FilterFromBandBoxSelection )
406+ return ;
407+ }
408+
409+ if (obj->Select ())
410+ {
411+ AllowVoice = false ;
412+
413+ /* *
414+ * If this is a new selection, filter it at the end.
415+ */
416+ if (TacticalExt::SelectedCount == 0 && !WWKeyboard->Down (VK_ALT ))
417+ TacticalExt::FilterSelection = true ;
418+ }
419+ }
420+
421+
188422/* *
189423 * #issue-315
190424 *
@@ -572,6 +806,10 @@ void TacticalExtension_Hooks()
572806 * @authors: CCHyper
573807 */
574808 Patch_Dword (0x006171C8 +1 , (TPF_CENTER |TPF_EFNT |TPF_FULLSHADOW ));
809+
575810 Patch_Jump (0x00616FDA , &_Tactical_Draw_Waypoint_Paths_Text_Color_Patch);
576811 Patch_Jump (0x00616560 , &TacticalExt::_Draw_Band_Box);
812+
813+ Patch_Jump (0x00616940 , &TacticalExt::_Select_These);
814+ Patch_Jump (0x00479150 , &Vinifera_Bandbox_Select);
577815}
0 commit comments