45
45
#include " fatal.h"
46
46
#include " debughandler.h"
47
47
#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
+
48
55
#include < timeapi.h>
49
56
50
57
#include " hooker.h"
51
58
#include " hooker_macros.h"
59
+ #include " technotypeext.h"
52
60
#include " uicontrol.h"
53
61
54
62
@@ -65,8 +73,23 @@ class TacticalExt : public Tactical
65
73
{
66
74
public:
67
75
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;
68
87
};
69
88
89
+ bool TacticalExt::SelectionContainsNonCombatants = false ;
90
+ int TacticalExt::SelectedCount = 0 ;
91
+ bool TacticalExt::FilterSelection = false ;
92
+
70
93
71
94
/* *
72
95
* Reimplements Tactical::Draw_Band_Box.
@@ -185,6 +208,217 @@ void TacticalExt::_Draw_Band_Box()
185
208
}
186
209
187
210
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
+
188
422
/* *
189
423
* #issue-315
190
424
*
@@ -572,6 +806,10 @@ void TacticalExtension_Hooks()
572
806
* @authors: CCHyper
573
807
*/
574
808
Patch_Dword (0x006171C8 +1 , (TPF_CENTER|TPF_EFNT|TPF_FULLSHADOW));
809
+
575
810
Patch_Jump (0x00616FDA , &_Tactical_Draw_Waypoint_Paths_Text_Color_Patch);
576
811
Patch_Jump (0x00616560 , &TacticalExt::_Draw_Band_Box);
812
+
813
+ Patch_Jump (0x00616940 , &TacticalExt::_Select_These);
814
+ Patch_Jump (0x00479150 , &Vinifera_Bandbox_Select);
577
815
}
0 commit comments