Skip to content

Instantly share code, notes, and snippets.

@trigger-segfault
Created November 11, 2021 02:24
Show Gist options
  • Select an option

  • Save trigger-segfault/cabe7a3dc5f8bf35d49e06efb0e10f5b to your computer and use it in GitHub Desktop.

Select an option

Save trigger-segfault/cabe7a3dc5f8bf35d49e06efb0e10f5b to your computer and use it in GitHub Desktop.
C++ Templated ListSet management for LEGO Rock Raiders.
#pragma once
#include "../../common.h"
#include "Errors.h"
#include "Memory.h"
/// TEMPLATE REQUIREMENTS:
// Note that the order of these fields is not important.
//
// struct TCont {
// TItem* listSet[MAXLISTS];
// TItem* freeList;
// uint32 listCount;
// };
//
// struct TItem {
// TItem* nextFree; // Normally the last field, but there isn't any hard requirement.
// };
//
// bool FPredicate(const TItem* item);
namespace ListSet
{; // !<---
#pragma region ListSet Template Helpers
/**
* @brief Template to obtain a listSet container type's element type.
* @param TCont The container type holding a listSet.
*/
template <class TCont>
using container_value_t = typename std::remove_pointer_t<std::remove_extent_t<decltype(TCont::listSet)>>;
/**
* @brief Template filter function typedef that obtains its value_type from the listSet container type.
*/
template <class TCont>
using predicate_f = bool(const container_value_t<TCont>*);
#pragma endregion
#pragma region ListSet Math
/**
* @brief Returns the maximum number of lists in a type's listSet.
* @param TCont The container type holding a listSet.
* @return The count of TCont's C array listSet.
*/
template <typename TCont>
constexpr const size_t MaxLists()
{
return _countof(TCont::listSet);
}
/**
* @brief Returns the total number of items available in a listSet with N lists.
* @param listCount The number of lists in a listSet (total or currently allocated).
* @return The combined size of listCount lists.
*/
constexpr const size_t CapacityOfLists(size_t listCount)
{
return ((static_cast<size_t>(1U) << listCount) - 1U);
}
/**
* @brief Returns the number of items available in a single list at index N in a listSet.
* @param listIndex The index of the list in a listSet.
* @return The combined size of listCount lists.
*/
constexpr const size_t CountOfList(size_t listIndex)
{
return (static_cast<size_t>(1U) << listIndex);
}
/**
* @brief Returns the index of an item in a specific listSet list.
* Should only used when the item is known to exist in the specified list.
* @param listIndex The index of the list in a listSet.
* @return The index of the item from the start of the list (calculated with pointer math).
*/
template <typename TItem>
constexpr const size_t IndexInList(const TItem* list, const TItem* item)
{
return (static_cast<size_t>(item - list) / sizeof(TItem));
}
/**
* @brief Returns the absolute index of an item in a listSet.
* @param listIndex The index of the list in a listSet that holds the item.
* @param itemIndex The index of the item in the list.
* @return The itemIndex + the capacity of all previous lists.
*/
constexpr const size_t IDInListSet(size_t listIndex, size_t itemIndex)
{
// Capacity of all previous lists + index in current list
return CapacityOfLists(listIndex) + itemIndex;
}
template <typename TItem>
constexpr const size_t IDInListSet(size_t listIndex, const TItem* list, const TItem* item)
{
return IDInListSet(listIndex, IndexInList(list, item));
}
#pragma endregion
#pragma region ListSet Item State
/**
* @brief Tests if a listSet item is alive.
* @param TItem The listSet item type.
* @param item An item created by the listSet.
* @return True if the item is alive, and managed by the user.
*/
template <typename TItem>
bool IsAlive(const TItem* item)
{
return (item->nextFree == item);
}
/**
* @brief Tests if a listSet item is dead.
* @param TItem The listSet item type.
* @param item An item created by the listSet.
* @return True if the item is dead, and managed by the listSet.
*/
template <typename TItem>
bool IsDead(const TItem* item)
{
return (item->nextFree != item);
}
/**
* @brief Tests if a listSet item is null or dead (shorthand for !item && IsDead(item)).
* @param TItem The listSet item type.
* @param item An item created by the listSet.
* @return True if the item is null, or is dead, and managed by the listSet.
*/
template <typename TItem>
bool IsNullOrDead(const TItem* item)
{
return (!item || item->nextFree != item);
}
#if 0
enum class ItemState : uint32
{
None = 0,
Dead = 0x1,
Alive = 0x2,
Any = Dead | Alive,
};
template <typename TItem>
bool CheckItemState(const TItem* item, ItemState state)
{
#pragma region Bit fiddling truth table
// Truth table for bit fiddling operation:
// ((ItemState::None) 0x0 & (IsDead)(1 << false)0x1) -> 0x0 (false)
// ((ItemState::None) 0x0 & (IsAlive)(1 << true)0x2) -> 0x0 (false)
// ((ItemState::Dead) 0x1 & (IsDead)(1 << false)0x1) -> 0x1 (true)
// ((ItemState::Dead) 0x1 & (IsAlive)(1 << true)0x2) -> 0x0 (false)
// ((ItemState::Alive)0x2 & (IsDead)(1 << false)0x1) -> 0x0 (false)
// ((ItemState::Alive)0x2 & (IsAlive)(1 << true)0x2) -> 0x2 (true)
// ((ItemState::Any) 0x3 & (IsDead)(1 << false)0x1) -> 0x1 (true)
// ((ItemState::Any) 0x3 & (IsAlive)(1 << true)0x2) -> 0x2 (true)
#pragma endregion
// Bit fiddling solution:
return ((uint32)state & (1 << (uint32)ListSet::IsAlive(item)));
// Identical to the following logic (assuming ItemState is a valid enum value):
/*
if (state == ItemState::Any) return true;
if (state == ItemState::None) return false;
return (state == ItemState::Alive) == ListSet::IsAlive(item);
*/
}
#endif
#pragma endregion
#pragma region ListSet Filter Functions
/**
* @brief A listSet filter function to return all items, alive or dead.
* @param TItem The value_type of a listSet container.
*/
template <typename TItem>
bool NoFilter(const TItem* item) { return true; }
/**
* @brief A listSet filter function to only return items that are alive, and managed by the user.
* @param TItem The value_type of a listSet container.
*/
template <typename TItem>
bool AliveFilter(const TItem* item) { return ListSet::IsAlive(item); }
/**
* @brief A listSet filter function to only return items that are dead, and managed by the listSet.
* @param TItem The value_type of a listSet container.
*/
template <typename TItem>
bool DeadFilter(const TItem* item) { return !ListSet::IsAlive(item); }
#pragma endregion
#pragma region ListSet Iterator
/**
* @brief The base iterator type for ListSet collections. (use Iterator or ReverseIterator)
* @param TCont The container type holding a listSet.
* @param FPredicate A filter function to skip past items where false is returned.
* @param Reverse Only use in combination with std::reverse_iterator<>.
* Changes the search direction for the first valid item during the constructor.
*/
template <typename TCont, const predicate_f<TCont> FPredicate, const bool Reverse>
class BaseIterator
{
public:
using container = TCont;
using difference_type = std::ptrdiff_t;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = container_value_t<container>;
using iterator = BaseIterator<container, FPredicate, Reverse>;
static constexpr size_t npos = static_cast<size_t>(-1);
/**
* @brief Construct an iterator at the specified list/item index in a listSet.
* @param cont The listSet container (i.e. Config_Globs configGlobs).
* @param listIndex The index of the list in cont's listSet that holds the item.
* @param itemIndex The index of the item in the list.
*/
BaseIterator(const container& cont, ptrdiff_t listIndex, ptrdiff_t itemIndex)
: m_cont(cont), m_listIndex(listIndex), m_itemIndex(0), m_itemCount(0), m_ptr(nullptr)
{
// Only perform validation (and assign itemIndex) if we're inside the listSet bounds.
// Otherwise this is a terminator iterator: i.e. end() or rend()
if (listIndex >= 0 && listIndex < static_cast<ptrdiff_t>(ListSet::MaxLists<container>())) {
// Pre-check for null list:
// When a list is null, inner count and index are 0 so that we instantly skip during increment/decrement.
value_type* list = m_cont.listSet[listIndex];
// Also check that we're searching within the bounds of listCount.
// The bounds check for listCount is placed here rather than where MaxLists is in order to:
// - increment up to end() (aka MaxLists) in the case of a forward iterator.
// - decrement to the next valid item in the case of a reverse iterator.
if (list && listIndex < static_cast<ptrdiff_t>(m_cont.listCount)) {
m_itemCount = ListSet::CountOfList(listIndex);
m_itemIndex = (itemIndex >= 0 ? itemIndex : (m_itemCount + itemIndex));
m_ptr = &list[m_itemIndex];
}
// Go to our first valid item:
// Null pointer means we automatically need to check for the next.
// But we also need to filter towards our first valid item, even when non-null.
if (!m_ptr || !FPredicate(m_ptr)) {
if (Reverse) --(*this);
else ++(*this);
}
}
}
// Iterator evaluation operator requirements:
/**
* @brief Returns a pointer to the current item in the listSet.
* Note that operator `It->` also returns the same pointer level.
* @return A pointer to the current item.
*/
value_type* operator*() const { return m_ptr; }
/**
* @brief Returns a pointer to the current item in the listSet.
* Note that operator `*It` also returns the same pointer level.
* @return A pointer to the current item to be accessed.
*/
value_type* operator->() const { return m_ptr; }
// Forward directional increment operator requirements:
// Prefix increment
iterator& operator++()
{
do {
// Increment our item index and check if we need to move to the next list.
if (++m_itemIndex >= static_cast<ptrdiff_t>(m_itemCount)) {
// Find next non-null list, and terminate inside if we reach the end of the listSet.
do {
// We can safely skip any remaining unused lists here.
if (++m_listIndex >= static_cast<ptrdiff_t>(m_cont.listCount)) {
// Always jump to end at MaxLists, rather than listCount.
// This way we can preserve Gods98 list iteration,
// where foreach loops will always iterate over newly-appended lists as it goes.
m_listIndex = ListSet::MaxLists<container>();
m_itemIndex = 0;
m_itemCount = 0;
m_ptr = nullptr;
return *this; // end of listSet
}
// We want to skip null lists. Realistically all remaining lists would also be null.
} while (!m_cont.listSet[m_listIndex]);
m_itemCount = ListSet::CountOfList(m_listIndex);
m_itemIndex = 0;
}
// Update our pointer to the current item. (alternatively can be done after do-while loop)
m_ptr = &m_cont.listSet[m_listIndex][m_itemIndex];
// Can we return our current item? (generally used to check if item is alive in the listSet).
} while (!FPredicate(m_ptr));
return *this;
}
// Postfix increment
iterator operator++(int)
{
iterator tmp = *this;
++(*this);
return tmp;
}
// Bidirectional iterator decrement operator requirements:
// Prefix decrement
iterator& operator--()
{
do {
// Decrement our item index and check if we need to move to the previous list.
if (m_itemIndex-- <= 0) {
// Find previous non-null listand terminate inside if we reach the beginning of the listSet.
do {
if (m_listIndex-- <= 0) {
// Always end at list -1, to signal we've reached the beginning of the listSet.
m_listIndex = -1;
m_itemIndex = 0;
m_itemCount = 0;
m_ptr = nullptr;
return *this; // pre-beginning of listSet
}
// We want to skip null lists, and also skip anything after listCount.
} while (!m_cont.listSet[m_listIndex] || m_listIndex >= static_cast<ptrdiff_t>(m_cont.listCount));
m_itemCount = ListSet::CountOfList(m_listIndex);
m_itemIndex = (m_itemCount - 1);
}
// Update our pointer to the current item. (could alternatively be done after the do-while loop)
m_ptr = &m_cont.listSet[m_listIndex][m_itemIndex];
// Can we return our current item? (generally used to check if item is alive in the listSet).
} while (!FPredicate(m_ptr));
return *this;
}
// Postfix decrement
iterator operator--(int)
{
iterator tmp = *this;
--(*this);
return tmp;
}
/**
* @brief Gets the index of the current list in the listSet.
*/
ptrdiff_t ListIndex() const { return m_listIndex; }
/**
* @brief Gets the index of the current item within the current list.
*/
ptrdiff_t ItemIndex() const { return m_itemIndex; }
/**
* @brief Gets the absolute index of the current item in the listSet.
* @return The absolute index of the current item, or npos if an item is not currently pointed to (e.g. end()).
*/
size_t ID() const { return (!m_ptr ? npos : ListSet::IDInListSet(m_listIndex, m_itemIndex)); }
/**
* @brief Tests if the current item is alive, and managed by the user.
*/
bool IsAlive() const { return ListSet::IsAlive(m_ptr); }
/**
* @brief Tests if the current item is dead, and managed by the listSet.
*/
bool IsDead() const { return !ListSet::IsAlive(m_ptr); }
// Foreach comparison operator requirements:
friend bool operator== (const iterator& a, const iterator& b) { return a.m_itemIndex == b.m_itemIndex && a.m_listIndex == b.m_listIndex; };
friend bool operator!= (const iterator& a, const iterator& b) { return a.m_itemIndex != b.m_itemIndex || a.m_listIndex != b.m_listIndex; };
protected:
const container& m_cont;
ptrdiff_t m_listIndex; // Index of the current list in the listSet.
ptrdiff_t m_itemIndex; // Index of the current item in the list.
size_t m_itemCount; // Size of the current list.
value_type* m_ptr; // Pointer to the current item.
};
/**
* @brief A forward iterator type for ListSet collections.
* @param TCont The container type holding a listSet.
* @param FPredicate A filter function to skip past items where false is returned.
*/
template <typename TCont, const predicate_f<TCont> FPredicate>
using Iterator = BaseIterator<TCont, FPredicate, false>;
/**
* @brief A reverse iterator type for ListSet collections.
* @param TCont The container type holding a listSet.
* @param FPredicate A filter function to skip past items where false is returned.
*/
template <typename TCont, const predicate_f<TCont> FPredicate>
using ReverseIterator = std::reverse_iterator<BaseIterator<TCont, FPredicate, true>>;
#pragma endregion
#pragma region ListSet Enumerable
/**
* @brief Template class for iterating over a listSet container's elements in a foreach loop.
* @param TCont The container type holding a listSet.
* @param FPredicate A filter function to skip past items where false is returned.
*/
template <typename TCont, const predicate_f<TCont> FPredicate>
class Enumerable
{
public:
using container = TCont;
using value_type = container_value_t<container>;
using iterator = Iterator<container, FPredicate>;
using reverse_iterator = ReverseIterator<container, FPredicate>;
/**
* @brief Constructs an enumerable collection wrapper around an existing listSet struct.
* @param cont The listSet container (i.e. Config_Globs configGlobs).
*/
Enumerable(container& cont)
: m_cont(cont)
{
}
/**
* @brief Gets the number of lists currently allocated in the listSet.
*/
size_t ListCount() const { return m_cont.listCount; }
/**
* @brief Gets the total number of lists that can be allocated in the listSet.
*/
constexpr size_t const MaxLists() const { return ListSet::MaxLists<container>(); }
/**
* @brief Returns the capacity of the currently allocated lists in the listSet.
*/
size_t Capacity() const { return ListSet::CapacityOfLists(m_cont.listCount); }
/**
* @brief Returns the total capacity of the listSet if all lists are currently allocated.
*/
constexpr size_t const MaxCapacity() const { return ListSet::CapacityOfLists(ListSet::MaxLists<container>); }
iterator begin() { return iterator(m_cont, 0, 0); }
const iterator cbegin() const { return iterator(m_cont, 0, 0); }
iterator end() { return iterator(m_cont, ListSet::MaxLists<container>(), 0); }
const iterator cend() const { return iterator(m_cont, ListSet::MaxLists<container>(), 0); }
reverse_iterator rbegin() { return reverse_iterator(m_cont, m_cont.listCount - 1, -1); }
const reverse_iterator crbegin() const { return reverse_iterator(m_cont, m_cont.listCount - 1, -1); }
reverse_iterator rend() { return reverse_iterator(m_cont, -1, -1); }
const reverse_iterator crend() const { return reverse_iterator(m_cont, -1, -1); }
protected:
container& m_cont;
};
/**
* @brief Template class for iterating over a listSet container's elements in a foreach loop (with no filter).
* @param TCont The container type holding a listSet.
*/
template <typename TCont>
using DefaultEnumerable = Enumerable<TCont, NoFilter<container_value_t<TCont>>>;
#pragma endregion
#pragma region ListSet Collection
/**
* @brief Template class for managing a listSet collection.
* @param TCont The container type holding a listSet.
*/
template <typename TCont>
class Collection : public DefaultEnumerable<TCont>
{
public:
using DefaultEnumerable<TCont>::m_cont; // Included so that `m_cont` member can be accessed without `this->`.
using container = TCont;
using value_type = container_value_t<container>;
template <const predicate_f<container> FPredicate>
using enumerable = Enumerable<container, FPredicate>;
/**
* @brief Constructs a listSet collection manager wrapped around an existing listSet struct.
* @param cont The listSet container (i.e. Config_Globs configGlobs).
*/
Collection(container& cont)
: DefaultEnumerable<TCont>(cont)
{
}
/**
* @brief Tests if a listSet item is alive.
* @param item An item created by the listSet.
* @return True if the item is alive, and managed by the user.
*/
bool IsAlive(value_type* item) { return ListSet::IsAlive(item); }
/**
* @brief Tests if a listSet item is dead.
* @param item An item created by the listSet.
* @return True if the item is dead, and managed by the listSet.
*/
bool IsDead(value_type* item) { return !ListSet::IsAlive(item); }
/**
* @brief Tests if a listSet item is null or dead (shorthand for !item && IsDead(item)).
* @param item An item created by the listSet.
* @return True if the item is null, or is dead, and managed by the listSet.
*/
bool IsNullOrDead(value_type* item) { return !item || !ListSet::IsAlive(item); }
/**
* @brief Returns an enumerable for iterating over filtered items in the listSet.
* @param FPredicate A filter function to skip past items where false is returned.
*/
template <const predicate_f<container> FPredicate>
enumerable<FPredicate> EnumerateWhere() { return enumerable<FPredicate>(m_cont); }
/**
* @brief Returns an enumerable for iterating over alive items in the listSet.
*/
enumerable<AliveFilter<value_type>> EnumerateAlive() { return enumerable<AliveFilter<value_type>>(m_cont); }
/**
* @brief Returns an enumerable for iterating over dead items in the listSet.
*/
enumerable<DeadFilter<value_type>> EnumerateDead() { return enumerable<DeadFilter<value_type>>(m_cont); }
/**
* @brief Returns an enumerable for iterating over every item in the listSet.
*/
enumerable<NoFilter<value_type>> Enumerate() { return enumerable<NoFilter<value_type>>(m_cont); }
/**
* @brief Returns the number of items that are currently alive in the listSet.
*/
size_t CountAlive() { return static_cast<size_t>(std::distance(this->begin(), this->end())); }
/**
* @brief Performs initial listSet setup by zeroing all appropriate fields.
*/
void Initialise()
{
for (size_t i = 0; i < this->MaxLists(); i++) {
m_cont.listSet[i] = nullptr;
}
m_cont.listCount = 0;
m_cont.freeList = nullptr;
}
/**
* @brief Performs listSet cleanup by freeing allocated lists and zeroing all appropriate fields.
* Note that the user should handle cleanup of individual list items beforehand.
*/
void Shutdown()
{
for (size_t i = 0; i < this->MaxLists(); i++) {
if (m_cont.listSet[i]) {
Gods98::Mem_Free(m_cont.listSet[i]);
m_cont.listSet[i] = nullptr;
}
}
// NOTE: LegoRR ListSet shutdowns often do not reset one or both these fields.
m_cont.listCount = 0;
m_cont.freeList = nullptr;
}
/**
* @brief Reserves and returns a new item in the listSet.
* @param memZero The item's memory will be zeroed out before being returned.
* @return A new living item in the listSet.
*/
value_type* Add(bool memZero)
{
if (m_cont.freeList == nullptr) this->AddList();
value_type* newItem = m_cont.freeList;
m_cont.freeList = newItem->nextFree;
if (memZero) std::memset(newItem, 0, sizeof(value_type));
newItem->nextFree = newItem;
return newItem;
}
/**
* @brief Remove a living item from the listSet.
* @param dead An item previously created by the listSet that has yet to be removed.
*/
void Remove(value_type* dead)
{
Error_Fatal(!dead, "NULL passed to ListSet::Remove");
Error_Fatal(!ListSet::IsAlive(dead), "Dead item passed to ListSet::Remove");
dead->nextFree = m_cont.freeList;
m_cont.freeList = dead;
}
protected:
/**
* @brief Adds a new list to the listSet. Only call this when `m_cont.freeList == nullptr`.
*/
void AddList()
{
Error_Fatal(m_cont.listCount + 1 >= this->MaxLists(), "Run out of lists");
size_t count = ListSet::CountOfList(m_cont.listCount);
value_type* list = m_cont.listSet[m_cont.listCount] = (value_type*)Gods98::Mem_Alloc(sizeof(value_type) * count);
if (list) {
m_cont.listCount++;
// Connect all items in a forward-linked-list fashion.
for (size_t i = 1; i < count; i++) {
list[i - 1].nextFree = &list[i];
}
// Finally, connect our newly allocated list to the remainder of the listSet.
// NOTE: freeList is always null when calling AddList (under normal circumstances).
list[count - 1].nextFree = m_cont.freeList;
m_cont.freeList = list;
}
else Error_Fatal(true, Gods98::Error_Format("Unable to allocate %d bytes of memory for new list.\n", sizeof(value_type) * count));
}
//protected:
// container& m_cont;
};
#pragma endregion
}
#include "ListSet.hpp"
// Subclass of ListSet::Collection since SkipIgnoreMes is the enumeration method used 95% of the time.
class LegoObject_ListSet : public ListSet::Collection<LegoObject_Globs>
{
public:
LegoObject_ListSet(LegoObject_Globs& cont)
: ListSet::Collection<LegoObject_Globs>(cont)
{
}
private:
static bool FilterSkipIgnoreMes(const LegoObject* liveObj);
{
return ListSet::IsAlive(liveObj) && !(liveObj->flags3 & LIVEOBJ3_IGNOREME_UNK);
}
public:
LegoObject_ListSet::enumerable<FilterSkipIgnoreMes> EnumerateSkipIgnoreMes();
{
return this->EnumerateWhere<FilterSkipIgnoreMes>();
}
};
// <LegoRR.exe @004df790>
static LegoObject_Globs & objectGlobs = *(LegoObject_Globs*)0x004df790;
static LegoObject_ListSet objectListSet = LegoObject_ListSet(objectGlobs);
typedef bool32 (__cdecl* LegoObject_RunThroughListsCallback)(LegoObject* liveObj, void* data);
// <LegoRR.exe @00437a70>
bool32 __cdecl LegoRR::LegoObject_RunThroughListsSkipIgnoreMes(LegoObject_RunThroughListsCallback callback, void* data)
{
for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) {
if (callback(liveObj, data))
return true; // terminate run through listSet
}
return false;
}
// <LegoRR.exe @00437a90>
bool32 __cdecl LegoRR::LegoObject_RunThroughLists(LegoObject_RunThroughListsCallback callback, void* data, bool32 skipIgnoreMeObjs)
{
for (LegoObject* liveObj : objectListSet.EnumerateAlive()) {
// Secondary filter
// Note that all remaining calls to this function pass false for skipIgnoreMeObjs,
// so this can be removed, due to implementing LegoObject_RunThroughListsSkipIgnoreMes.
if (!skipIgnoreMeObjs || !(liveObj->flags3 & LIVEOBJ3_IGNOREME_UNK)) {
if (callback(liveObj, data))
return true; // terminate run through listSet
}
}
return false;
}
// <LegoRR.exe @0043b5e0>
void __cdecl LegoRR::LegoObject_RemoveAll(void)
{
objectGlobs.flags |= LegoObject_GlobFlags::OBJECT_GLOB_FLAG_REMOVING;
for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) {
// We can skip past LegoObject_Callback_Remove(liveObj,nullptr) and just call LegoObject_Remove directly.
LegoObject_Remove(liveObj);
}
// OLD: LegoObject_RunThroughListsSkipIgnoreMes(LegoObject_Callback_Remove, nullptr);
objectGlobs.flags &= ~LegoObject_GlobFlags::OBJECT_GLOB_FLAG_REMOVING;
}
// <LegoRR.exe @00449ec0>
void __cdecl LegoRR::LegoObject_AllCallback_FUN_00449ec0(void)
{
for (LegoObject* liveObj : objectListSet.EnumerateAlive()) {
LiveObject_Callback_FUN_00449fb0(liveObj, nullptr);
}
// OLD: LegoObject_RunThroughLists(LiveObject_Callback_FUN_00449fb0, nullptr, false);
}
// <LegoRR.exe @0044c810>
void __cdecl LegoRR::LegoObject_CameraCycleUnits(void)
{
// Cycle-able units have now been validated since level-start (once the function exits).
objectGlobs.flags |= LegoObject_GlobFlags::OBJECT_GLOB_FLAG_CYCLEUNITS;
// Start by trying to cycle through buildings.
bool32 noBuildings = false;
for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) {
if (LegoObject_Callback_CameraCycleFindUnit(liveObj, &noBuildings))
return; // New building added to cycle list and camera moved. We're done.
}
// If no buildings are constructed, then cycle through minifigure unit types instead.
if (objectGlobs.cycleBuildingCount == 0) {
// Change cycle unit type to minifigures.
noBuildings = true;
for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) {
if (LegoObject_Callback_CameraCycleFindUnit(liveObj, &noBuildings))
return; // New minifigure added to cycle list and camera moved. We're done.
}
}
// If cycle-able units exist, then all valid units were already in the list.
if (objectGlobs.cycleUnitCount != 0) {
// Reset the cycle list, then jump to the first available unit of our cycle unit type (building or minifigure).
// This behavior is used so that the cycle list is guaranteed to not repeat itself until all units
// have been visited once. After all units have been visited, the list is started anew.
// (This effectively functions like a music shuffle, without the intention of shuffling.)
// Note that the order of units may change if new valid units were created in the middle of a cycle,
// This is because the order of the cycle is first and foremost based on where the object is in the listSet,
// And the listSet is not intended to be ordered based on when a unit is added, as freed up slots earlier in
// the list may be used instead.
objectGlobs.cycleUnitCount = 0;
objectGlobs.cycleBuildingCount = 0;
// Find first unit to cycle to.
for (LegoObject* liveObj : objectListSet.EnumerateSkipIgnoreMes()) {
if (LegoObject_Callback_CameraCycleFindUnit(liveObj, &noBuildings))
return; // First building/minifigure added to cycle list and camera moved. We're done.
}
}
}
// <LegoRR.exe @0044c8b0>
bool32 __cdecl LegoRR::LegoObject_Callback_CameraCycleFindUnit(LegoObject* liveObj, OPTIONAL void* pNoBuildings)
{
// Only check for units of a specific object type:
// (defaults to Buildings when null)
if (pNoBuildings != nullptr && *(bool32*)pNoBuildings) {
if (liveObj->type != LegoObject_MiniFigure) return false;
}
else {
if (liveObj->type != LegoObject_Building) return false;
}
// Don't check for units already in cycleUnits:
for (uint32 loop = 0; loop < objectGlobs.cycleUnitCount; loop++) {
if (objectGlobs.cycleUnits[loop] == liveObj)
return false;
}
if (objectGlobs.cycleUnitCount < _countof(objectGlobs.cycleUnits)) {
objectGlobs.cycleUnits[objectGlobs.cycleUnitCount++] = liveObj;
if (liveObj->type == LegoObject_Building)
objectGlobs.cycleBuildingCount++;
// Name is misleading, this just goes to a location using the same method as Info icons.
Lego_GotoInfo(liveObj, nullptr, false);
uint32 bx, by; // dummy outputs
Vector3F worldPos;
if (Map3D_GetIntersections(legoGlobs.currLevel->map, legoGlobs.viewMain, Gods98::msx(), Gods98::msy(), &bx, &by, &worldPos)) {
Gods98::Container_SetPosition(legoGlobs.rootLight, nullptr, worldPos.x, worldPos.y, worldPos.z - 250.0f);
return true;
}
}
else {
objectGlobs.cycleUnitCount = 0;
objectGlobs.cycleBuildingCount = 0;
}
return true; // Is returning true here intentional?
// So cycle functionality breaks after 256 building or minifigure units I guess...?
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment