Skip to content

Commit 88b545e

Browse files
authored
bugfix(controls): Fix and improve next idle worker selection with contained workers (#1338)
1 parent d042104 commit 88b545e

File tree

6 files changed

+98
-42
lines changed

6 files changed

+98
-42
lines changed

Core/GameEngine/Include/Common/STLUtils.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include <Utility/CppMacros.h>
2222
#include <utility>
23+
#include <algorithm>
2324

2425
namespace stl
2526
{
@@ -58,4 +59,18 @@ bool find_and_erase_unordered(Container& container, const typename Container::va
5859
return false;
5960
}
6061

62+
// Push back value into vector-like container if it does not yet contain that value.
63+
template <typename Container>
64+
bool push_back_unique(Container& container, const typename Container::value_type& value)
65+
{
66+
typename Container::iterator it = std::find(container.begin(), container.end(), value);
67+
if (it == container.end())
68+
{
69+
container.push_back(value);
70+
return true;
71+
}
72+
73+
return false;
74+
}
75+
6176
} // namespace stl

Generals/Code/GameEngine/Include/GameClient/InGameUI.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,12 @@ class InGameUI : public SubsystemInterface, public Snapshot
309309
{
310310

311311
friend class Drawable; // for selection/deselection transactions
312-
312+
313+
protected:
314+
315+
typedef std::list<Object*> ObjectList;
316+
typedef std::list<Object*>::iterator ObjectListIt;
317+
313318
public: // ***************************************************************************************
314319

315320
enum SelectionRules
@@ -544,6 +549,7 @@ friend class Drawable; // for selection/deselection transactions
544549
virtual void addIdleWorker( Object *obj );
545550
virtual void removeIdleWorker( Object *obj, Int playerNumber );
546551
virtual void selectNextIdleWorker( void );
552+
static std::vector<Object*> getUniqueIdleWorkers(const ObjectList& idleWorkers);
547553

548554
virtual void recreateControlBar( void );
549555
virtual void refreshCustomUiResources( void );
@@ -650,9 +656,6 @@ friend class Drawable; // for selection/deselection transactions
650656
Color color; ///< what color should we display the military subtitles
651657
};
652658

653-
typedef std::list<Object *> ObjectList;
654-
typedef std::list<Object *>::iterator ObjectListIt;
655-
656659
// ----------------------------------------------------------------------------------------------
657660
// Protected Methods ----------------------------------------------------------------------------
658661
// ----------------------------------------------------------------------------------------------

Generals/Code/GameEngine/Include/GameLogic/GameLogic.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ typedef void (*GameLogicFuncPtr)( Object *obj, void *userData );
9393
typedef std::hash_map<ObjectID, Object *, rts::hash<ObjectID>, rts::equal_to<ObjectID> > ObjectPtrHash;
9494
typedef ObjectPtrHash::const_iterator ObjectPtrIter;
9595

96+
typedef std::vector<Object*> ObjectPtrVector;
9697

9798
// ------------------------------------------------------------------------------------------------
9899
/**

Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5424,42 +5424,41 @@ void InGameUI::selectNextIdleWorker( void )
54245424
if(getSelectCount() == 0 || getSelectCount() > 1)
54255425
{
54265426
selectThisObject = *m_idleWorkers[index].begin();
5427+
// If our idle worker is contained by anything, we need to select the container instead.
5428+
while (selectThisObject->getContainedBy())
5429+
selectThisObject = selectThisObject->getContainedBy();
54275430
}
54285431
else
54295432
{
5430-
Drawable *selectedDrawable = TheInGameUI->getFirstSelectedDrawable();
5431-
5432-
ObjectListIt it = m_idleWorkers[index].begin();
5433-
while(it != m_idleWorkers[index].end())
5433+
Drawable *selectedDrawable = TheInGameUI->getFirstSelectedDrawable();
5434+
// TheSuperHackers @tweak Stubbjax 22/07/2025 Idle worker iteration now correctly identifies and
5435+
// iterates contained idle workers. Previous iteration logic would not go past contained workers,
5436+
// and was not guaranteed to select top-level containers.
5437+
ObjectPtrVector uniqueIdleWorkers = getUniqueIdleWorkers(m_idleWorkers[index]);
5438+
5439+
ObjectPtrVector::iterator it = uniqueIdleWorkers.begin();
5440+
while(it != uniqueIdleWorkers.end())
54345441
{
54355442
Object *itObj = *it;
54365443
if(itObj == selectedDrawable->getObject())
54375444
{
54385445
++it;
5439-
if(it != m_idleWorkers[index].end())
5446+
if(it != uniqueIdleWorkers.end())
54405447
selectThisObject = *it;
54415448
else
5442-
selectThisObject = *m_idleWorkers[index].begin();
5449+
selectThisObject = *uniqueIdleWorkers.begin();
54435450
break;
54445451
}
54455452
++it;
54465453
}
54475454
// if we had something selected that wasn't a worker, we'll get here
5448-
if(!selectThisObject)
5449-
selectThisObject = *m_idleWorkers[index].begin();
5450-
5455+
if (!selectThisObject)
5456+
selectThisObject = uniqueIdleWorkers.front();
54515457
}
54525458
DEBUG_ASSERTCRASH(selectThisObject, ("InGameUI::selectNextIdleWorker Could not select the next IDLE worker"));
54535459
if(selectThisObject)
54545460
{
5455-
5456-
//If our idle worker is contained by anything, we need to select the container instead.
5457-
Object *containedBy = selectThisObject->getContainedBy();
5458-
if( containedBy )
5459-
{
5460-
selectThisObject = containedBy;
5461-
}
5462-
5461+
DEBUG_ASSERTCRASH(selectThisObject->getContainedBy() == NULL, ("InGameUI::selectNextIdleWorker Selected idle object should not be contained"));
54635462
deselectAllDrawables();
54645463
GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP );
54655464

@@ -5484,6 +5483,24 @@ void InGameUI::selectNextIdleWorker( void )
54845483
}
54855484
}
54865485

5486+
// Finds unique selectables to avoid selecting the same or a previous container if multiple idle workers are contained.
5487+
ObjectPtrVector InGameUI::getUniqueIdleWorkers(const ObjectList& idleWorkers)
5488+
{
5489+
ObjectPtrVector uniqueIdleWorkers;
5490+
uniqueIdleWorkers.reserve(idleWorkers.size());
5491+
5492+
for (ObjectList::const_iterator it = idleWorkers.begin(); it != idleWorkers.end(); ++it)
5493+
{
5494+
Object* itObj = *it;
5495+
while (itObj->getContainedBy())
5496+
itObj = itObj->getContainedBy();
5497+
5498+
stl::push_back_unique(uniqueIdleWorkers, itObj);
5499+
}
5500+
5501+
return uniqueIdleWorkers;
5502+
}
5503+
54875504
Int InGameUI::getIdleWorkerCount( void )
54885505
{
54895506
Int index = ThePlayerList->getLocalPlayer()->getPlayerIndex();

GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,12 @@ class InGameUI : public SubsystemInterface, public Snapshot
323323
{
324324

325325
friend class Drawable; // for selection/deselection transactions
326-
326+
327+
protected:
328+
329+
typedef std::list<Object*> ObjectList;
330+
typedef std::list<Object*>::iterator ObjectListIt;
331+
327332
public: // ***************************************************************************************
328333

329334
enum SelectionRules
@@ -561,6 +566,7 @@ friend class Drawable; // for selection/deselection transactions
561566
virtual void addIdleWorker( Object *obj );
562567
virtual void removeIdleWorker( Object *obj, Int playerNumber );
563568
virtual void selectNextIdleWorker( void );
569+
static std::vector<Object*> getUniqueIdleWorkers(const ObjectList& idleWorkers);
564570

565571
virtual void recreateControlBar( void );
566572
virtual void refreshCustomUiResources( void );
@@ -670,9 +676,6 @@ friend class Drawable; // for selection/deselection transactions
670676
Color color; ///< what color should we display the military subtitles
671677
};
672678

673-
typedef std::list<Object *> ObjectList;
674-
typedef std::list<Object *>::iterator ObjectListIt;
675-
676679
// ----------------------------------------------------------------------------------------------
677680
// Protected Methods ----------------------------------------------------------------------------
678681
// ----------------------------------------------------------------------------------------------

GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5596,42 +5596,41 @@ void InGameUI::selectNextIdleWorker( void )
55965596
if(getSelectCount() == 0 || getSelectCount() > 1)
55975597
{
55985598
selectThisObject = *m_idleWorkers[index].begin();
5599+
// If our idle worker is contained by anything, we need to select the container instead.
5600+
while (selectThisObject->getContainedBy())
5601+
selectThisObject = selectThisObject->getContainedBy();
55995602
}
56005603
else
56015604
{
5602-
Drawable *selectedDrawable = TheInGameUI->getFirstSelectedDrawable();
5603-
5604-
ObjectListIt it = m_idleWorkers[index].begin();
5605-
while(it != m_idleWorkers[index].end())
5605+
Drawable *selectedDrawable = TheInGameUI->getFirstSelectedDrawable();
5606+
// TheSuperHackers @tweak Stubbjax 22/07/2025 Idle worker iteration now correctly identifies and
5607+
// iterates contained idle workers. Previous iteration logic would not go past contained workers,
5608+
// and was not guaranteed to select top-level containers.
5609+
ObjectPtrVector uniqueIdleWorkers = getUniqueIdleWorkers(m_idleWorkers[index]);
5610+
5611+
ObjectPtrVector::iterator it = uniqueIdleWorkers.begin();
5612+
while(it != uniqueIdleWorkers.end())
56065613
{
56075614
Object *itObj = *it;
56085615
if(itObj == selectedDrawable->getObject())
56095616
{
56105617
++it;
5611-
if(it != m_idleWorkers[index].end())
5618+
if(it != uniqueIdleWorkers.end())
56125619
selectThisObject = *it;
56135620
else
5614-
selectThisObject = *m_idleWorkers[index].begin();
5621+
selectThisObject = *uniqueIdleWorkers.begin();
56155622
break;
56165623
}
56175624
++it;
56185625
}
56195626
// if we had something selected that wasn't a worker, we'll get here
5620-
if(!selectThisObject)
5621-
selectThisObject = *m_idleWorkers[index].begin();
5622-
5627+
if (!selectThisObject)
5628+
selectThisObject = uniqueIdleWorkers.front();
56235629
}
56245630
DEBUG_ASSERTCRASH(selectThisObject, ("InGameUI::selectNextIdleWorker Could not select the next IDLE worker"));
56255631
if(selectThisObject)
56265632
{
5627-
5628-
//If our idle worker is contained by anything, we need to select the container instead.
5629-
Object *containedBy = selectThisObject->getContainedBy();
5630-
if( containedBy )
5631-
{
5632-
selectThisObject = containedBy;
5633-
}
5634-
5633+
DEBUG_ASSERTCRASH(selectThisObject->getContainedBy() == NULL, ("InGameUI::selectNextIdleWorker Selected idle object should not be contained"));
56355634
deselectAllDrawables();
56365635
GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP );
56375636

@@ -5656,6 +5655,24 @@ void InGameUI::selectNextIdleWorker( void )
56565655
}
56575656
}
56585657

5658+
// Finds unique selectables to avoid selecting the same or a previous container if multiple idle workers are contained.
5659+
ObjectPtrVector InGameUI::getUniqueIdleWorkers(const ObjectList& idleWorkers)
5660+
{
5661+
ObjectPtrVector uniqueIdleWorkers;
5662+
uniqueIdleWorkers.reserve(idleWorkers.size());
5663+
5664+
for (ObjectList::const_iterator it = idleWorkers.begin(); it != idleWorkers.end(); ++it)
5665+
{
5666+
Object* itObj = *it;
5667+
while (itObj->getContainedBy())
5668+
itObj = itObj->getContainedBy();
5669+
5670+
stl::push_back_unique(uniqueIdleWorkers, itObj);
5671+
}
5672+
5673+
return uniqueIdleWorkers;
5674+
}
5675+
56595676
Int InGameUI::getIdleWorkerCount( void )
56605677
{
56615678
Int index = ThePlayerList->getLocalPlayer()->getPlayerIndex();

0 commit comments

Comments
 (0)