From 29bc21a24206ee76b3460c4d74d24092753c04cb Mon Sep 17 00:00:00 2001 From: superp00t Date: Fri, 6 Sep 2024 12:22:01 -0400 Subject: [PATCH] feat(cursor): add cursor module that stores and updates various cursor images --- src/cursor/CMakeLists.txt | 17 +++ src/cursor/Cursor.cpp | 218 ++++++++++++++++++++++++++++++++++++++ src/cursor/Cursor.hpp | 13 +++ src/cursor/Types.hpp | 69 ++++++++++++ 4 files changed, 317 insertions(+) create mode 100644 src/cursor/CMakeLists.txt create mode 100644 src/cursor/Cursor.cpp create mode 100644 src/cursor/Cursor.hpp create mode 100644 src/cursor/Types.hpp diff --git a/src/cursor/CMakeLists.txt b/src/cursor/CMakeLists.txt new file mode 100644 index 0000000..6ed0f48 --- /dev/null +++ b/src/cursor/CMakeLists.txt @@ -0,0 +1,17 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +add_library(cursor STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(cursor + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(cursor + PRIVATE + storm + gx + ui +) diff --git a/src/cursor/Cursor.cpp b/src/cursor/Cursor.cpp new file mode 100644 index 0000000..72bdfd3 --- /dev/null +++ b/src/cursor/Cursor.cpp @@ -0,0 +1,218 @@ +#include "cursor/Cursor.hpp" +#include "ui/FrameScript.hpp" +#include "gx/Texture.hpp" +#include "gx/Device.hpp" + +#include +#include + +#include + +static CURSORMODE s_cursorMode; +static uint32_t s_cursorResetMode; +static CURSORITEMTYPE s_cursorItemType; +static uint32_t s_cursorItemMips; +static uint32_t s_cursorItemMipsHeight; +static uint32_t s_cursorItemMipsWidth; +static int8_t s_cursorFile; + +static uint32_t s_cursorItem[CURSOR_IMAGE_SIZE]; + +static uint32_t s_cursorImages[NUM_CURSOR_MODES][CURSOR_IMAGE_SIZE]; + +static const char* s_cursorNames[NUM_CURSOR_MODES] = { + nullptr, /* NO_CURSOR */ + "Point", /* POINT_CURSOR */ + "Cast", /* CAST_CURSOR */ + "Buy", /* BUY_CURSOR */ + "Attack", /* ATTACK_CURSOR */ + "Interact", /* INTERACT_CURSOR */ + "Speak", /* SPEAK_CURSOR */ + "Inspect", /* INSPECT_CURSOR */ + "Pickup", /* PICKUP_CURSOR */ + "Taxi", /* TAXI_CURSOR */ + "Trainer", /* TRAINER_CURSOR */ + "Mine", /* MINE_CURSOR */ + "Skin", /* SKIN_CURSOR */ + "GatherHerbs", /* GATHER_CURSOR */ + "PickLock", /* LOCK_CURSOR */ + "Mail", /* MAIL_CURSOR */ + "LootAll", /* LOOT_ALL_CURSOR */ + "Repair", /* REPAIR_CURSOR */ + "RepairNPC", /* REPAIRNPC_CURSOR */ + "Item", /* ITEM_CURSOR */ + "SkinHorde", /* SKIN_HORDE_CURSOR */ + "SkinAlliance", /* SKIN_ALLIANCE_CURSOR */ + "Innkeeper", /* INNKEEPER_CURSOR */ + "Quest", /* QUEST_CURSOR */ + "QuestRepeatable", /* QUEST_REPEATABLE_CURSOR */ + "QuestTurnIn", /* QUEST_TURNIN_CURSOR */ + "vehichleCursor", /* VEHICLE_CURSOR */ + "UnablePoint", /* POINT_ERROR_CURSOR */ + "UnableCast", /* CAST_ERROR_CURSOR */ + "UnableBuy", /* BUY_ERROR_CURSOR */ + "UnableAttack", /* ATTACK_ERROR_CURSOR */ + "UnableInteract", /* INTERACT_ERROR_CURSOR */ + "UnableSpeak", /* SPEAK_ERROR_CURSOR */ + "UnableInspect", /* INSPECT_ERROR_CURSOR */ + "UnablePickup", /* PICKUP_ERROR_CURSOR */ + "UnableTaxi", /* TAXI_ERROR_CURSOR */ + "UnableTrainer", /* TRAINER_ERROR_CURSOR */ + "UnableMine", /* MINE_ERROR_CURSOR */ + "UnableSkin", /* SKIN_ERROR_CURSOR */ + "UnableGatherHerbs", /* GATHER_ERROR_CURSOR */ + "UnablePickLock", /* LOCK_ERROR_CURSOR */ + "UnableMail", /* MAIL_ERROR_CURSOR */ + "UnableLootAll", /* LOOT_ALL_ERROR_CURSOR */ + "UnableRepair", /* REPAIR_ERROR_CURSOR */ + "UnableRepairNPC", /* REPAIRNPC_ERROR_CURSOR */ + "UnableItem", /* ITEM_ERROR_CURSOR */ + "UnableSkinHorde", /* SKIN_HORDE_ERROR_CURSOR */ + "UnableSkinAlliance", /* SKIN_ALLIANCE_ERROR_CURSOR */ + "UnableInnkeeper", /* INNKEEPER_ERROR_CURSOR */ + "UnableQuest", /* QUEST_ERROR_CURSOR */ + "UnableQuestRepeatable", /* QUEST_REPEATABLE_ERROR_CURSOR */ + "UnableQuestTurnIn", /* QUEST_TURNIN_ERROR_CURSOR */ + "UnablevehichleCursor" /* VEHICLE_ERROR_CURSOR */ +}; + +int32_t CopyCursorImage(uint32_t* image, MipBits* mipImages, uint32_t width, uint32_t height) { + if (mipImages) { + if (width == 32 && height == 32) { + memcpy(image, mipImages->mip[0], CURSOR_IMAGE_BYTES); + return 1; + } + + if (width == 64 && height == 64) { + // Downsample /2 + + auto end = image + 1024; + + auto topRow = mipImages->mip[0]; + + auto dest = image; + + while (dest < end) { + auto bottomRow = topRow + 64; + + auto topPixels = topRow; + auto bottomPixels = bottomRow; + + // Merge 4 pixels into one + while (bottomPixels < bottomRow) { + *dest++ = ((topPixels[0].b + topPixels[1].b + bottomPixels[0].b + bottomPixels[1].b) / 4) | + (((topPixels[0].g + topPixels[1].g + bottomPixels[0].g + bottomPixels[1].g) / 4) << 8) | + (((topPixels[0].r + topPixels[1].r + bottomPixels[0].r + bottomPixels[1].r) / 4) << 16) | + (((topPixels[0].a + topPixels[1].a + bottomPixels[0].a + bottomPixels[1].a) / 4) << 24); + + topPixels += 2; + bottomPixels += 2; + } + + // Advance two rows + topRow += 128; + } + + return 1; + } + } + + // failure + + memset(image, 0, CURSOR_IMAGE_BYTES); + return 0; +} + +void UpdateCursor() { + auto cursor = g_theGxDevicePtr->CursorLock(); + + if (s_cursorItemType == CURSOR_EMPTY) { + // update the cursor to the current mode's image + memcpy(cursor, s_cursorImages[s_cursorMode], CURSOR_IMAGE_BYTES); + } else { + if (s_cursorItemMips == 0) { + // merge the icon with the cursor bitmap + // so the pointer in the top left remains + int32_t i = 0; + int32_t j = CURSOR_IMAGE_SIZE/4; + + while (j) { + auto pixel1 = s_cursorImages[CURSOR_ITEM][i]; + if (!(pixel1 & 0xFF000000)) { + pixel1 = s_cursorItem[i]; + } + cursor[i] = pixel1; + + auto pixel2 = s_cursorImages[CURSOR_ITEM][i + 1]; + if (!(pixel2 & 0xFF000000)) { + pixel2 = s_cursorItem[i + 1]; + } + cursor[i + 1] = pixel2; + + auto pixel3 = s_cursorImages[CURSOR_ITEM][i + 2]; + if (!(pixel3 & 0xFF000000)) { + pixel3 = s_cursorItem[i + 2]; + } + cursor[i + 2] = pixel3; + + auto pixel4 = s_cursorImages[CURSOR_ITEM][i + 3]; + if (!(pixel4 & 0xFF000000)) { + pixel4 = s_cursorItem[i + 3]; + } + cursor[i + 3] = pixel4; + + j--; + } + } else { + memcpy(cursor, s_cursorImages[CURSOR_ITEM], CURSOR_IMAGE_BYTES); + } + } + + g_theGxDevicePtr->CursorUnlock(0, 0); +} + +void CursorInitialize() { + s_cursorMode = POINT_CURSOR; + s_cursorResetMode = 1; + s_cursorItemType = CURSOR_EMPTY; + s_cursorItemMips = 0; + s_cursorItemMipsHeight = 0; + s_cursorItemMipsWidth = 0; + + char cursorFilename[STORM_MAX_PATH]; + + for (uint32_t mode = 0; mode < NUM_CURSOR_MODES; mode++) { + auto cursorName = s_cursorNames[mode]; + auto cursorImage = s_cursorImages[mode]; + + if (cursorName) { + SStrPrintf(cursorFilename, STORM_MAX_PATH, "Interface\\Cursor\\%s.blp", cursorName); + + uint32_t width; + uint32_t height; + PIXEL_FORMAT dataFormat = PIXEL_ARGB8888; + + auto mipImages = TextureLoadImage(cursorFilename, &width, &height, &dataFormat, nullptr, nullptr, nullptr, 0); + + if (mipImages) { + CopyCursorImage(cursorImage, mipImages, width, height); + TextureFreeMippedImg(mipImages, dataFormat, width, height); + } else { + memset(cursorImage, 0, CURSOR_IMAGE_BYTES); + } + } else { + memset(cursorImage, 0, CURSOR_IMAGE_BYTES); + } + } + + s_cursorFile = 0; + UpdateCursor(); +} + +void CursorSetMode(CURSORMODE mode) { + if (mode != NUM_CURSOR_MODES && s_cursorMode != mode) { + s_cursorMode = mode; + UpdateCursor(); + FrameScript_SignalEvent(275, nullptr); + } +} diff --git a/src/cursor/Cursor.hpp b/src/cursor/Cursor.hpp new file mode 100644 index 0000000..ca5187a --- /dev/null +++ b/src/cursor/Cursor.hpp @@ -0,0 +1,13 @@ +#ifndef CURSOR_CURSOR_HPP +#define CURSOR_CURSOR_HPP + +#define CURSOR_IMAGE_SIZE (32 * 32) +#define CURSOR_IMAGE_BYTES (CURSOR_IMAGE_SIZE * 4) + +#include "cursor/Types.hpp" + +void CursorInitialize(); + +void CursorSetMode(CURSORMODE mode); + +#endif diff --git a/src/cursor/Types.hpp b/src/cursor/Types.hpp new file mode 100644 index 0000000..e7945fb --- /dev/null +++ b/src/cursor/Types.hpp @@ -0,0 +1,69 @@ +#ifndef CURSOR_TYPES_HPP +#define CURSOR_TYPES_HPP + +enum CURSORITEMTYPE { + CURSOR_EMPTY = 0, + CURSOR_ITEM = 1, + CURSOR_MONEY = 2, + CURSOR_SPELL = 3, + NUM_CURSOR_ITEM_TYPES = 4 +}; + +enum CURSORMODE { + NO_CURSOR = 0, + POINT_CURSOR = 1, + CAST_CURSOR = 2, + BUY_CURSOR = 3, + ATTACK_CURSOR = 4, + INTERACT_CURSOR = 5, + SPEAK_CURSOR = 6, + INSPECT_CURSOR = 7, + PICKUP_CURSOR = 8, + TAXI_CURSOR = 9, + TRAINER_CURSOR = 10, + MINE_CURSOR = 11, + SKIN_CURSOR = 12, + GATHER_CURSOR = 13, + LOCK_CURSOR = 14, + MAIL_CURSOR = 15, + LOOT_ALL_CURSOR = 16, + REPAIR_CURSOR = 17, + REPAIRNPC_CURSOR = 18, + ITEM_CURSOR = 19, + SKIN_HORDE_CURSOR = 20, + SKIN_ALLIANCE_CURSOR = 21, + INNKEEPER_CURSOR = 22, + QUEST_CURSOR = 23, + QUEST_REPEATABLE_CURSOR = 24, + QUEST_TURNIN_CURSOR = 25, + VEHICLE_CURSOR = 26, + POINT_ERROR_CURSOR = 27, + CAST_ERROR_CURSOR = 28, + BUY_ERROR_CURSOR = 29, + ATTACK_ERROR_CURSOR = 30, + INTERACT_ERROR_CURSOR = 31, + SPEAK_ERROR_CURSOR = 32, + INSPECT_ERROR_CURSOR = 33, + PICKUP_ERROR_CURSOR = 34, + TAXI_ERROR_CURSOR = 35, + TRAINER_ERROR_CURSOR = 36, + MINE_ERROR_CURSOR = 37, + SKIN_ERROR_CURSOR = 38, + GATHER_ERROR_CURSOR = 39, + LOCK_ERROR_CURSOR = 40, + MAIL_ERROR_CURSOR = 41, + LOOT_ALL_ERROR_CURSOR = 42, + REPAIR_ERROR_CURSOR = 43, + REPAIRNPC_ERROR_CURSOR = 44, + ITEM_ERROR_CURSOR = 45, + SKIN_HORDE_ERROR_CURSOR = 46, + SKIN_ALLIANCE_ERROR_CURSOR = 47, + INNKEEPER_ERROR_CURSOR = 48, + QUEST_ERROR_CURSOR = 49, + QUEST_REPEATABLE_ERROR_CURSOR = 50, + QUEST_TURNIN_ERROR_CURSOR = 51, + VEHICLE_ERROR_CURSOR = 52, + NUM_CURSOR_MODES = 53 +}; + +#endif