Skip to content

Commit

Permalink
fix comparison entity not being drawn in level view, improve level vi…
Browse files Browse the repository at this point in the history
…ew optimisation, make memory view in entity "Memory" tab scroll together with the comparison entity memory
  • Loading branch information
Mr-Auto committed May 25, 2024
1 parent 8cde57e commit 2099853
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 60 deletions.
18 changes: 12 additions & 6 deletions include/Data/StdMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ 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);
set_offsets();
};

// 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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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<Key, uintptr_t> operator*()
{
return {key(), value_ptr()};
}
Node operator++()
{
if (is_nil())
Expand Down Expand Up @@ -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<Key, Value>* parent_map;
};

Expand Down
26 changes: 22 additions & 4 deletions include/QtHelpers/WidgetSpelunkyLevel.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,26 @@
#include <QSize>
#include <QWidget>
#include <array>
#include <bitset>
#include <cstdint>
#include <utility>
#include <vector>

namespace S2Plugin
{
class EntityToPaint
{
public:
EntityToPaint() = default;
EntityToPaint(uintptr_t addr, QBrush col) : ent(addr), color(col){};

protected:
Entity ent;
QBrush color;
std::pair<float, float> pos{0, 0};
friend class WidgetSpelunkyLevel;
};

class WidgetSpelunkyLevel : public QWidget
{
Q_OBJECT
Expand All @@ -28,30 +42,34 @@ namespace S2Plugin
void paintFloor(const QColor& color);
void clearAllPaintedEntities();
void clearPaintedEntity(uintptr_t addr);
void updateLevel();

protected:
void paintEvent(QPaintEvent* event) override;

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<uintptr_t, uintptr_t> mMaskMapAddr;
std::pair<uintptr_t, uintptr_t> mGridEntitiesAddr;

uint32_t mEntityMasksToPaint{0};
std::array<QBrush, 15> mEntityMaskColors;
std::vector<std::pair<Entity, QBrush>> mEntitiesToPaint;
std::array<std::vector<std::pair<float, float>>, 15> mEntitiesMaskCoordinates;
std::vector<EntityToPaint> 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
1 change: 1 addition & 0 deletions include/Views/ViewEntity.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ namespace S2Plugin
// TAB MEMORY
WidgetMemoryView* mMemoryView;
WidgetMemoryView* mMemoryComparisonView;
QScrollArea* mMemoryScrollArea;
QScrollArea* mMemoryComparisonScrollArea;

// TAB LEVEL
Expand Down
125 changes: 81 additions & 44 deletions src/QtHelpers/WidgetSpelunkyLevel.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "QtHelpers/WidgetSpelunkyLevel.h"

#include "Configuration.h"
#include "Data/StdMap.h"
#include "Spelunky2.h"
Expand All @@ -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*)
Expand All @@ -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)
{
Expand All @@ -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));
}

Expand Down Expand Up @@ -126,14 +115,17 @@ 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();
}

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;
Expand All @@ -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<uint32_t, size_t> 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<uintptr_t> 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();
}
24 changes: 18 additions & 6 deletions src/Views/ViewEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QScrollBar>
#include <QStandardItemModel>
#include <QVBoxLayout>
#include <string>
Expand Down Expand Up @@ -115,24 +116,33 @@ 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);
mMemoryComparisonScrollArea->setStyleSheet("background-color: #fff;");
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);
}
Expand Down Expand Up @@ -167,7 +177,7 @@ void S2Plugin::ViewEntity::refreshEntity()
}
else if (mMainTabWidget->currentIndex() == TABS::LEVEL)
{
mSpelunkyLevel->update();
mSpelunkyLevel->updateLevel();
}
}

Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 2099853

Please sign in to comment.