diff --git a/include/Data/StdMap.h b/include/Data/StdMap.h index 5c14f05..96853f6 100644 --- a/include/Data/StdMap.h +++ b/include/Data/StdMap.h @@ -16,7 +16,7 @@ namespace S2Plugin struct StdMap { // only for the template - StdMap(size_t addr) : address(addr) + StdMap(uintptr_t addr) : address(addr) { keytype_size = sizeof(Key); valuetype_size = sizeof(Value); @@ -24,13 +24,13 @@ namespace S2Plugin }; // value size only needed for value() function - StdMap(size_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize) : address(addr) + StdMap(uintptr_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize) : address(addr) { keytype_size = keySize; valuetype_size = sizeof(Value); set_offsets(keyAlignment, valueAlignment); }; - StdMap(size_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize, size_t valueSize) : address(addr) + StdMap(uintptr_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize, size_t valueSize) : address(addr) { keytype_size = keySize; valuetype_size = valueSize; @@ -72,11 +72,11 @@ namespace S2Plugin } return (Value)Script::Memory::ReadQword(value_address); } - size_t key_ptr() const + uintptr_t key_ptr() const { return node_ptr + parent_map->key_offset; } - size_t value_ptr() const + uintptr_t value_ptr() const { return node_ptr + parent_map->value_offset; } @@ -103,6 +103,11 @@ namespace S2Plugin { return (bool)Script::Memory::ReadByte(node_ptr + 0x19); } + // returning value ptr instead of value itself since it's more usefull for us + std::pair operator*() + { + return {key(), value_ptr()}; + } Node operator++() { if (is_nil()) @@ -159,9 +164,10 @@ namespace S2Plugin { return other.node_ptr != node_ptr; } - size_t node_ptr; private: + uintptr_t node_ptr; + // need reference to the map object so we can get offsets and alignments const StdMap* parent_map; }; diff --git a/include/QtHelpers/WidgetSpelunkyLevel.h b/include/QtHelpers/WidgetSpelunkyLevel.h index 9ff1714..b4d0e59 100644 --- a/include/QtHelpers/WidgetSpelunkyLevel.h +++ b/include/QtHelpers/WidgetSpelunkyLevel.h @@ -6,12 +6,26 @@ #include #include #include +#include #include #include #include namespace S2Plugin { + class EntityToPaint + { + public: + EntityToPaint() = default; + EntityToPaint(uintptr_t addr, QBrush col) : ent(addr), color(col){}; + + protected: + Entity ent; + QBrush color; + std::pair pos{0, 0}; + friend class WidgetSpelunkyLevel; + }; + class WidgetSpelunkyLevel : public QWidget { Q_OBJECT @@ -28,6 +42,7 @@ namespace S2Plugin void paintFloor(const QColor& color); void clearAllPaintedEntities(); void clearPaintedEntity(uintptr_t addr); + void updateLevel(); protected: void paintEvent(QPaintEvent* event) override; @@ -35,23 +50,26 @@ namespace S2Plugin private: uintptr_t mMainEntityAddr{0}; - bool mPaintFloors{false}; QBrush mFloorColor; - uint mLevelWidth{0}; - uint mLevelHeight{0}; + bool mPaintFloors{false}; + // uint8_t mLevelWidth{0}; + // uint8_t mLevelHeight{0}; std::pair mMaskMapAddr; std::pair mGridEntitiesAddr; uint32_t mEntityMasksToPaint{0}; std::array mEntityMaskColors; - std::vector> mEntitiesToPaint; + std::array>, 15> mEntitiesMaskCoordinates; + std::vector mEntitiesToPaint; static constexpr uint8_t msLevelMaxHeight = 125; static constexpr uint8_t msLevelMaxWidth = 85; static constexpr uint8_t msMarginVer = 1; static constexpr uint8_t msMarginHor = 1; static constexpr uint8_t msScaleFactor = 7; + + uintptr_t mLevelFloors[msLevelMaxHeight + 1][msLevelMaxWidth + 1] = {}; }; } // namespace S2Plugin diff --git a/include/Views/ViewEntity.h b/include/Views/ViewEntity.h index 7775730..2e386fd 100644 --- a/include/Views/ViewEntity.h +++ b/include/Views/ViewEntity.h @@ -49,6 +49,7 @@ namespace S2Plugin // TAB MEMORY WidgetMemoryView* mMemoryView; WidgetMemoryView* mMemoryComparisonView; + QScrollArea* mMemoryScrollArea; QScrollArea* mMemoryComparisonScrollArea; // TAB LEVEL diff --git a/src/QtHelpers/WidgetSpelunkyLevel.cpp b/src/QtHelpers/WidgetSpelunkyLevel.cpp index 31ed0ee..979814b 100644 --- a/src/QtHelpers/WidgetSpelunkyLevel.cpp +++ b/src/QtHelpers/WidgetSpelunkyLevel.cpp @@ -1,4 +1,5 @@ #include "QtHelpers/WidgetSpelunkyLevel.h" + #include "Configuration.h" #include "Data/StdMap.h" #include "Spelunky2.h" @@ -13,21 +14,27 @@ S2Plugin::WidgetSpelunkyLevel::WidgetSpelunkyLevel(uintptr_t main_entity, QWidge mGridEntitiesAddr.first = Configuration::get()->offsetForField(MemoryFieldType::State, "layer0.grid_entities_begin", stateptr); mGridEntitiesAddr.second = Configuration::get()->offsetForField(MemoryFieldType::State, "layer1.grid_entities_begin", stateptr); - auto offset = Configuration::get()->offsetForField(MemoryFieldType::State, "level_width_rooms", stateptr); - mLevelWidth = Script::Memory::ReadDword(offset) * 10; - mLevelHeight = Script::Memory::ReadDword(offset + 4) * 8; - - if (mLevelWidth == 0) - { - mLevelWidth = msLevelMaxWidth; - mLevelHeight = msLevelMaxHeight; - } - else - { - // add border size - mLevelWidth += 5; - mLevelHeight += 5; - } + // auto offset = Configuration::get()->offsetForField(MemoryFieldType::State, "level_width_rooms", stateptr); + // mLevelWidth = Script::Memory::ReadDword(offset) * 10; + // mLevelHeight = Script::Memory::ReadDword(offset + 4) * 8; + + // if (mLevelWidth == 0) + //{ + // mLevelWidth = msLevelMaxWidth; + // mLevelHeight = msLevelMaxHeight; + // } + // else + //{ + // // add border size + // mLevelWidth += 5; + // mLevelHeight += 5; + + // // limit just in case + // if (mLevelWidth > msLevelMaxWidth) + // mLevelWidth = msLevelMaxWidth; + // if (mLevelHeight > msLevelMaxHeight) + // mLevelHeight = msLevelMaxHeight; + //} } void S2Plugin::WidgetSpelunkyLevel::paintEvent(QPaintEvent*) @@ -45,20 +52,14 @@ void S2Plugin::WidgetSpelunkyLevel::paintEvent(QPaintEvent*) painter.setPen(Qt::transparent); if (mPaintFloors) { - // painting floors is quite expensive, any optimisations are always welcome (like maybe use provided event to not draw obstructed parts of the level?) - // TODO: don't read on refresh rate + // painting floors is quite expensive, any optimisations are always welcome (like maybe use provided event to not draw obstructed parts of the level view?) painter.setBrush(mFloorColor); - auto gridAddr = layerToDraw == 0 ? mGridEntitiesAddr.first : mGridEntitiesAddr.second; // y: 0-125, x: 0-85 // note: the y = 125 is at the top of a level and the level is build from the top - for (uint8_t y = msLevelMaxHeight - mLevelHeight; y < 126; ++y) - { - for (uint8_t x = 0; x <= mLevelWidth; ++x) - { - if (Script::Memory::ReadQword(gridAddr + y * (86 * sizeof(uintptr_t)) + x * sizeof(uintptr_t)) != 0) + for (uint8_t y = 0; y < msLevelMaxHeight + 1; ++y) + for (uint8_t x = 0; x <= msLevelMaxWidth + 1; ++x) + if (mLevelFloors[y][x] != 0) painter.drawRect(QRectF(msMarginHor + x, msMarginVer + msLevelMaxHeight - y, 1.0, 1.0)); - } - } } if (mEntityMasksToPaint != 0) { @@ -67,29 +68,17 @@ void S2Plugin::WidgetSpelunkyLevel::paintEvent(QPaintEvent*) { if ((mEntityMasksToPaint >> bit_number) & 1) { - auto itr = maskMap.find(1u << bit_number); - if (itr != maskMap.end()) - { - painter.setBrush(mEntityMaskColors[bit_number]); - // TODO: change to proper struct when done - auto ent_list = itr.value_ptr(); - auto pointers = Script::Memory::ReadQword(ent_list); - auto list_count = Script::Memory::ReadDword(ent_list + 20); - for (uint idx = 0; idx < list_count; idx++) - { - Entity ent{Script::Memory::ReadQword(pointers + idx * sizeof(uintptr_t))}; - auto [entityX, entityY] = ent.abs_position(); - painter.drawRect(QRectF(msMarginHor + entityX, msMarginVer + msLevelMaxHeight - entityY, 1.0, 1.0)); - } - } + painter.setBrush(mEntityMaskColors[bit_number]); + for (auto& [posX, posY] : mEntitiesMaskCoordinates[bit_number]) + painter.drawRect(QRectF(msMarginHor + posX, msMarginVer + msLevelMaxHeight - posY, 1.0, 1.0)); } } } - for (auto& [entity, color] : mEntitiesToPaint) + for (auto& entity : mEntitiesToPaint) { - auto [entityX, entityY] = entity.abs_position(); - painter.setBrush(color); + const auto& [entityX, entityY] = entity.pos; + painter.setBrush(entity.color); painter.drawRect(QRectF(msMarginHor + entityX, msMarginVer + msLevelMaxHeight - entityY, 1.0, 1.0)); } @@ -126,6 +115,9 @@ void S2Plugin::WidgetSpelunkyLevel::clearAllPaintedEntities() mEntitiesToPaint.clear(); mEntityMasksToPaint = 0; mPaintFloors = false; + for (uint8_t bit_number = 0; bit_number < mEntityMaskColors.size(); ++bit_number) + mEntitiesMaskCoordinates[bit_number].clear(); + update(); } @@ -133,7 +125,7 @@ void S2Plugin::WidgetSpelunkyLevel::clearPaintedEntity(uintptr_t addr) { for (auto cur = mEntitiesToPaint.begin(); cur < mEntitiesToPaint.end(); ++cur) { - if (cur->first.ptr() == addr) + if (cur->ent.ptr() == addr) { mEntitiesToPaint.erase(cur); break; @@ -152,3 +144,48 @@ QSize S2Plugin::WidgetSpelunkyLevel::sizeHint() const { return minimumSizeHint(); } + +void S2Plugin::WidgetSpelunkyLevel::updateLevel() +{ + uint8_t layerToDraw = Entity{mMainEntityAddr}.layer(); + if (mPaintFloors) + { + auto gridAddr = layerToDraw == 0 ? mGridEntitiesAddr.first : mGridEntitiesAddr.second; + // Maybe don't read the whole array? + constexpr auto dataSize = (msLevelMaxHeight + 1) * ((msLevelMaxWidth + 1) * sizeof(uintptr_t)); + Script::Memory::Read(gridAddr, &mLevelFloors, dataSize, nullptr); + } + for (auto& entity : mEntitiesToPaint) + entity.pos = entity.ent.abs_position(); + + if (mEntityMasksToPaint != 0) + { + StdMap maskMap{layerToDraw == 0 ? mMaskMapAddr.first : mMaskMapAddr.second}; + for (auto [key, value_ptr] : maskMap) + { + uint8_t bit_number = std::log2(key); + mEntitiesMaskCoordinates[bit_number].clear(); + if ((mEntityMasksToPaint & key) != 0) + { + // TODO: change to proper struct when done + auto pointers = Script::Memory::ReadQword(value_ptr); + auto list_count = Script::Memory::ReadDword(value_ptr + 20); + + mEntitiesMaskCoordinates[bit_number].reserve(list_count); + std::vector entities; + entities.resize(list_count); + Script::Memory::Read(pointers, entities.data(), list_count * sizeof(uintptr_t), nullptr); + + for (auto entityAddr : entities) + { + if (entityAddr == 0) + continue; + + Entity ent{entityAddr}; + mEntitiesMaskCoordinates[bit_number].emplace_back(ent.abs_position()); + } + } + } + } + update(); +} diff --git a/src/Views/ViewEntity.cpp b/src/Views/ViewEntity.cpp index 9f98f73..b1376b7 100644 --- a/src/Views/ViewEntity.cpp +++ b/src/Views/ViewEntity.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -115,11 +116,11 @@ void S2Plugin::ViewEntity::initializeUI() } // TAB MEMORY { - auto scroll = new QScrollArea(tabMemory); - mMemoryView = new WidgetMemoryView(scroll); - scroll->setStyleSheet("background-color: #fff;"); - scroll->setWidget(mMemoryView); - tabMemory->layout()->addWidget(scroll); + mMemoryScrollArea = new QScrollArea(tabMemory); + mMemoryView = new WidgetMemoryView(mMemoryScrollArea); + mMemoryScrollArea->setStyleSheet("background-color: #fff;"); + mMemoryScrollArea->setWidget(mMemoryView); + tabMemory->layout()->addWidget(mMemoryScrollArea); mMemoryComparisonScrollArea = new QScrollArea(tabMemory); mMemoryComparisonView = new WidgetMemoryView(mMemoryComparisonScrollArea); @@ -127,12 +128,21 @@ void S2Plugin::ViewEntity::initializeUI() mMemoryComparisonScrollArea->setWidget(mMemoryComparisonView); tabMemory->layout()->addWidget(mMemoryComparisonScrollArea); mMemoryComparisonScrollArea->setVisible(false); + + connect(mMemoryScrollArea->horizontalScrollBar(), &QScrollBar::valueChanged, mMemoryComparisonScrollArea->horizontalScrollBar(), &QScrollBar::setValue); + connect(mMemoryScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, mMemoryComparisonScrollArea->verticalScrollBar(), &QScrollBar::setValue); + connect(mMemoryComparisonScrollArea->horizontalScrollBar(), &QScrollBar::valueChanged, mMemoryScrollArea->horizontalScrollBar(), &QScrollBar::setValue); + connect(mMemoryComparisonScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, mMemoryScrollArea->verticalScrollBar(), &QScrollBar::setValue); } // TAB LEVEL { mSpelunkyLevel = new WidgetSpelunkyLevel(mEntityPtr, tabLevel); mSpelunkyLevel->paintFloor(QColor(160, 160, 160)); mSpelunkyLevel->paintEntity(mEntityPtr, QColor(222, 52, 235)); + // to many entities + // mSpelunkyLevel->paintEntityMask(0x4000, QColor(255, 87, 6)); // lava + // mSpelunkyLevel->paintEntityMask(0x2000, QColor(6, 213, 249)); // water + mSpelunkyLevel->paintEntityMask(0x80, QColor(85, 170, 170)); // active floors tabLevel->setStyleSheet("background-color: #fff;"); tabLevel->setWidget(mSpelunkyLevel); } @@ -167,7 +177,7 @@ void S2Plugin::ViewEntity::refreshEntity() } else if (mMainTabWidget->currentIndex() == TABS::LEVEL) { - mSpelunkyLevel->update(); + mSpelunkyLevel->updateLevel(); } } @@ -334,8 +344,10 @@ void S2Plugin::ViewEntity::entityOffsetDropped(uintptr_t entityOffset) } mComparisonEntityPtr = entityOffset; + mSpelunkyLevel->paintEntity(entityOffset, QColor(232, 134, 30)); mMemoryComparisonScrollArea->setVisible(true); updateMemoryViewOffsetAndSize(); + mMemoryScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } void S2Plugin::ViewEntity::tabChanged()